From 1fe08956b75498e3255ac8059519d9e242c61bf8 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 26 Jun 2025 12:00:02 -0400 Subject: [PATCH 01/33] implemented socket-based dwds --- dwds/CHANGELOG.md | 7 + dwds/lib/dart_web_debug_service.dart | 56 +- dwds/lib/data/serializers.dart | 4 + dwds/lib/data/serializers.g.dart | 2 + dwds/lib/data/service_extension_request.dart | 43 + .../lib/data/service_extension_request.g.dart | 228 ++++ dwds/lib/data/service_extension_response.dart | 50 + .../data/service_extension_response.g.dart | 277 +++++ .../lib/src/connections/debug_connection.dart | 30 +- dwds/lib/src/handlers/dev_handler.dart | 304 +++-- dwds/lib/src/handlers/injector.dart | 17 +- dwds/lib/src/injected/client.js | 1003 +++++++++++++---- dwds/lib/src/services/app_debug_services.dart | 71 +- .../src/services/chrome_proxy_service.dart | 79 +- dwds/lib/src/services/debug_service.dart | 2 - .../web_socket_app_debug_services.dart | 53 + .../services/web_socket_debug_service.dart | 178 +++ .../services/web_socket_proxy_service.dart | 668 +++++++++++ dwds/lib/src/version.dart | 2 +- dwds/lib/src/web_socket_dwds_vm_client.dart | 196 ++++ dwds/pubspec.yaml | 2 +- dwds/web/client.dart | 74 +- .../ddc_library_bundle_restarter.dart | 30 + dwds/web/reloader/manager.dart | 14 + 24 files changed, 2964 insertions(+), 426 deletions(-) create mode 100644 dwds/lib/data/service_extension_request.dart create mode 100644 dwds/lib/data/service_extension_request.g.dart create mode 100644 dwds/lib/data/service_extension_response.dart create mode 100644 dwds/lib/data/service_extension_response.g.dart create mode 100644 dwds/lib/src/services/web_socket_app_debug_services.dart create mode 100644 dwds/lib/src/services/web_socket_debug_service.dart create mode 100644 dwds/lib/src/services/web_socket_proxy_service.dart create mode 100644 dwds/lib/src/web_socket_dwds_vm_client.dart diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 418ff21e7..ef2ab9164 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,3 +1,10 @@ +## 24.3.12-wip + +- Removed DWDS requirement for Chrome Debug Port by implementing a WebSocket-based communication protocol that provides essential developer tooling (hot reload, service extensions) when Chrome debugger access is unavailable. - [#2605](https://github.com/dart-lang/webdev/issues/2605) +- Added WebSocket-based hot reload and service extension support via new `WebSocketProxyService` class that implements VM service protocol over WebSockets. +- Created new files: `WebSocketProxyService`, `WebSocketDebugService`, `WebSocketAppDebugServices`, and `WebSocketDwdsVmClient` to support socket-based DWDS functionality. +- Enhanced `DevHandler` with `useWebSocketConnection` flag to toggle between Chrome-based and WebSocket-based communication protocols. + ## 24.3.11 - Changed DWDS to always inject the client and added `useDwdsWebSocketConnection` flag to control communication protocol: when true uses socket-based implementation, when false uses Chrome-based communication protocol. diff --git a/dwds/lib/dart_web_debug_service.dart b/dwds/lib/dart_web_debug_service.dart index ff960f0f0..31705ca3d 100644 --- a/dwds/lib/dart_web_debug_service.dart +++ b/dwds/lib/dart_web_debug_service.dart @@ -31,6 +31,7 @@ class Dwds { final DevHandler _devHandler; final AssetReader _assetReader; final bool _enableDebugging; + final bool _useDwdsWebSocketConnection; Dwds._( this.middleware, @@ -38,6 +39,7 @@ class Dwds { this._devHandler, this._assetReader, this._enableDebugging, + this._useDwdsWebSocketConnection, ) : handler = _devHandler.handler; Stream get connectedApps => _devHandler.connectedApps; @@ -53,14 +55,57 @@ class Dwds { await _assetReader.close(); } + /// Creates a debug connection for the given app connection. + /// + /// Returns a [DebugConnection] that wraps the appropriate debug services + /// based on the connection type (WebSocket or Chrome-based). Future debugConnection(AppConnection appConnection) async { if (!_enableDebugging) throw StateError('Debugging is not enabled.'); + final appDebugServices = await _devHandler.loadAppServices(appConnection); - final chromeProxyService = appDebugServices.chromeProxyService; - await chromeProxyService.isInitialized; + + // Initialize the appropriate proxy service based on connection type + if (_useDwdsWebSocketConnection) { + await _initializeWebSocketService(appDebugServices); + } else { + await _initializeChromeService(appDebugServices); + } + return DebugConnection(appDebugServices); } + /// Initializes and waits for WebSocket proxy service to be ready. + Future _initializeWebSocketService(dynamic appDebugServices) async { + try { + final webSocketProxyService = appDebugServices.webSocketProxyService; + if (webSocketProxyService != null) { + await webSocketProxyService.isInitialized; + _logger.fine('WebSocket proxy service initialized successfully'); + } else { + _logger.warning('WebSocket proxy service is null'); + } + } catch (e) { + _logger.severe('Failed to initialize WebSocket proxy service: $e'); + rethrow; + } + } + + /// Initializes and waits for Chrome proxy service to be ready. + Future _initializeChromeService(dynamic appDebugServices) async { + try { + final chromeProxyService = appDebugServices.chromeProxyService; + if (chromeProxyService != null) { + await chromeProxyService.isInitialized; + _logger.fine('Chrome proxy service initialized successfully'); + } else { + _logger.warning('Chrome proxy service is null'); + } + } catch (e) { + _logger.severe('Failed to initialize Chrome proxy service: $e'); + rethrow; + } + } + static Future start({ required AssetReader assetReader, required Stream buildResults, @@ -123,10 +168,7 @@ class Dwds { _logger.info('Serving DevTools at $uri\n'); } - final injected = DwdsInjector( - extensionUri: extensionUri, - useDwdsWebSocketConnection: useDwdsWebSocketConnection, - ); + final injected = DwdsInjector(extensionUri: extensionUri); final devHandler = DevHandler( chromeConnection, @@ -143,6 +185,7 @@ class Dwds { debugSettings.spawnDds, debugSettings.ddsPort, debugSettings.launchDevToolsInNewWindow, + useWebSocketConnection: useDwdsWebSocketConnection, ); return Dwds._( @@ -151,6 +194,7 @@ class Dwds { devHandler, assetReader, debugSettings.enableDebugging, + useDwdsWebSocketConnection, ); } } diff --git a/dwds/lib/data/serializers.dart b/dwds/lib/data/serializers.dart index 11755912f..4d6614f85 100644 --- a/dwds/lib/data/serializers.dart +++ b/dwds/lib/data/serializers.dart @@ -14,6 +14,8 @@ import 'error_response.dart'; import 'extension_request.dart'; import 'hot_reload_request.dart'; import 'hot_reload_response.dart'; +import 'service_extension_request.dart'; +import 'service_extension_response.dart'; import 'isolate_events.dart'; import 'register_event.dart'; import 'run_request.dart'; @@ -40,5 +42,7 @@ part 'serializers.g.dart'; ErrorResponse, RegisterEvent, RunRequest, + ServiceExtensionRequest, + ServiceExtensionResponse, ]) final Serializers serializers = _$serializers; diff --git a/dwds/lib/data/serializers.g.dart b/dwds/lib/data/serializers.g.dart index 3f229a323..a061bae9e 100644 --- a/dwds/lib/data/serializers.g.dart +++ b/dwds/lib/data/serializers.g.dart @@ -27,6 +27,8 @@ Serializers _$serializers = ..add(IsolateStart.serializer) ..add(RegisterEvent.serializer) ..add(RunRequest.serializer) + ..add(ServiceExtensionRequest.serializer) + ..add(ServiceExtensionResponse.serializer) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(DebugEvent)]), () => new ListBuilder(), diff --git a/dwds/lib/data/service_extension_request.dart b/dwds/lib/data/service_extension_request.dart new file mode 100644 index 000000000..7539a9b3b --- /dev/null +++ b/dwds/lib/data/service_extension_request.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2025, the Dart project authors. All rights reserved. +// Defines the request for service extension calls over WebSocket. + +import 'dart:convert'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +part 'service_extension_request.g.dart'; + +abstract class ServiceExtensionRequest + implements Built { + String get id; + String get method; + String + get argsJson; // Store args as JSON string for built_value compatibility + + // Helper method to get args as Map + Map get args => + argsJson.isEmpty + ? {} + : json.decode(argsJson) as Map; + + ServiceExtensionRequest._(); + factory ServiceExtensionRequest([ + void Function(ServiceExtensionRequestBuilder) updates, + ]) = _$ServiceExtensionRequest; + + // Convenient factory method to create with args Map + factory ServiceExtensionRequest.fromArgs({ + required String id, + required String method, + required Map args, + }) => ServiceExtensionRequest( + (b) => + b + ..id = id + ..method = method + ..argsJson = json.encode(args), + ); + + static Serializer get serializer => + _$serviceExtensionRequestSerializer; +} diff --git a/dwds/lib/data/service_extension_request.g.dart b/dwds/lib/data/service_extension_request.g.dart new file mode 100644 index 000000000..94379fd36 --- /dev/null +++ b/dwds/lib/data/service_extension_request.g.dart @@ -0,0 +1,228 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'service_extension_request.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$serviceExtensionRequestSerializer = + new _$ServiceExtensionRequestSerializer(); + +class _$ServiceExtensionRequestSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + ServiceExtensionRequest, + _$ServiceExtensionRequest, + ]; + @override + final String wireName = 'ServiceExtensionRequest'; + + @override + Iterable serialize( + Serializers serializers, + ServiceExtensionRequest object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(String)), + 'method', + serializers.serialize( + object.method, + specifiedType: const FullType(String), + ), + 'argsJson', + serializers.serialize( + object.argsJson, + specifiedType: const FullType(String), + ), + ]; + + return result; + } + + @override + ServiceExtensionRequest deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ServiceExtensionRequestBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'method': + result.method = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'argsJson': + result.argsJson = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + } + } + + return result.build(); + } +} + +class _$ServiceExtensionRequest extends ServiceExtensionRequest { + @override + final String id; + @override + final String method; + @override + final String argsJson; + + factory _$ServiceExtensionRequest([ + void Function(ServiceExtensionRequestBuilder)? updates, + ]) => (new ServiceExtensionRequestBuilder()..update(updates))._build(); + + _$ServiceExtensionRequest._({ + required this.id, + required this.method, + required this.argsJson, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(id, r'ServiceExtensionRequest', 'id'); + BuiltValueNullFieldError.checkNotNull( + method, + r'ServiceExtensionRequest', + 'method', + ); + BuiltValueNullFieldError.checkNotNull( + argsJson, + r'ServiceExtensionRequest', + 'argsJson', + ); + } + + @override + ServiceExtensionRequest rebuild( + void Function(ServiceExtensionRequestBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + ServiceExtensionRequestBuilder toBuilder() => + new ServiceExtensionRequestBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ServiceExtensionRequest && + id == other.id && + method == other.method && + argsJson == other.argsJson; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, method.hashCode); + _$hash = $jc(_$hash, argsJson.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'ServiceExtensionRequest') + ..add('id', id) + ..add('method', method) + ..add('argsJson', argsJson)) + .toString(); + } +} + +class ServiceExtensionRequestBuilder + implements + Builder { + _$ServiceExtensionRequest? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _method; + String? get method => _$this._method; + set method(String? method) => _$this._method = method; + + String? _argsJson; + String? get argsJson => _$this._argsJson; + set argsJson(String? argsJson) => _$this._argsJson = argsJson; + + ServiceExtensionRequestBuilder(); + + ServiceExtensionRequestBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _method = $v.method; + _argsJson = $v.argsJson; + _$v = null; + } + return this; + } + + @override + void replace(ServiceExtensionRequest other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$ServiceExtensionRequest; + } + + @override + void update(void Function(ServiceExtensionRequestBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ServiceExtensionRequest build() => _build(); + + _$ServiceExtensionRequest _build() { + final _$result = + _$v ?? + new _$ServiceExtensionRequest._( + id: BuiltValueNullFieldError.checkNotNull( + id, + r'ServiceExtensionRequest', + 'id', + ), + method: BuiltValueNullFieldError.checkNotNull( + method, + r'ServiceExtensionRequest', + 'method', + ), + argsJson: BuiltValueNullFieldError.checkNotNull( + argsJson, + r'ServiceExtensionRequest', + 'argsJson', + ), + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/dwds/lib/data/service_extension_response.dart b/dwds/lib/data/service_extension_response.dart new file mode 100644 index 000000000..854526951 --- /dev/null +++ b/dwds/lib/data/service_extension_response.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2025, the Dart project authors. All rights reserved. +// Defines the response for service extension calls over WebSocket. + +import 'dart:convert'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +part 'service_extension_response.g.dart'; + +abstract class ServiceExtensionResponse + implements + Built { + String get id; + String? + get resultJson; // Store result as JSON string for built_value compatibility + bool get success; + int? get errorCode; + String? get errorMessage; + + // Helper method to get result as Map + Map? get result => + resultJson == null || resultJson!.isEmpty + ? null + : json.decode(resultJson!) as Map; + + ServiceExtensionResponse._(); + factory ServiceExtensionResponse([ + void Function(ServiceExtensionResponseBuilder) updates, + ]) = _$ServiceExtensionResponse; + + // Convenient factory method to create with result Map + factory ServiceExtensionResponse.fromResult({ + required String id, + required bool success, + Map? result, + int? errorCode, + String? errorMessage, + }) => ServiceExtensionResponse( + (b) => + b + ..id = id + ..success = success + ..resultJson = result != null ? json.encode(result) : null + ..errorCode = errorCode + ..errorMessage = errorMessage, + ); + + static Serializer get serializer => + _$serviceExtensionResponseSerializer; +} diff --git a/dwds/lib/data/service_extension_response.g.dart b/dwds/lib/data/service_extension_response.g.dart new file mode 100644 index 000000000..e840cf2b4 --- /dev/null +++ b/dwds/lib/data/service_extension_response.g.dart @@ -0,0 +1,277 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'service_extension_response.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$serviceExtensionResponseSerializer = + new _$ServiceExtensionResponseSerializer(); + +class _$ServiceExtensionResponseSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + ServiceExtensionResponse, + _$ServiceExtensionResponse, + ]; + @override + final String wireName = 'ServiceExtensionResponse'; + + @override + Iterable serialize( + Serializers serializers, + ServiceExtensionResponse object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(String)), + 'success', + serializers.serialize( + object.success, + specifiedType: const FullType(bool), + ), + ]; + Object? value; + value = object.resultJson; + if (value != null) { + result + ..add('resultJson') + ..add( + serializers.serialize(value, specifiedType: const FullType(String)), + ); + } + value = object.errorCode; + if (value != null) { + result + ..add('errorCode') + ..add(serializers.serialize(value, specifiedType: const FullType(int))); + } + value = object.errorMessage; + if (value != null) { + result + ..add('errorMessage') + ..add( + serializers.serialize(value, specifiedType: const FullType(String)), + ); + } + return result; + } + + @override + ServiceExtensionResponse deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ServiceExtensionResponseBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'resultJson': + result.resultJson = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'success': + result.success = + serializers.deserialize( + value, + specifiedType: const FullType(bool), + )! + as bool; + break; + case 'errorCode': + result.errorCode = + serializers.deserialize(value, specifiedType: const FullType(int)) + as int?; + break; + case 'errorMessage': + result.errorMessage = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$ServiceExtensionResponse extends ServiceExtensionResponse { + @override + final String id; + @override + final String? resultJson; + @override + final bool success; + @override + final int? errorCode; + @override + final String? errorMessage; + + factory _$ServiceExtensionResponse([ + void Function(ServiceExtensionResponseBuilder)? updates, + ]) => (new ServiceExtensionResponseBuilder()..update(updates))._build(); + + _$ServiceExtensionResponse._({ + required this.id, + this.resultJson, + required this.success, + this.errorCode, + this.errorMessage, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + id, + r'ServiceExtensionResponse', + 'id', + ); + BuiltValueNullFieldError.checkNotNull( + success, + r'ServiceExtensionResponse', + 'success', + ); + } + + @override + ServiceExtensionResponse rebuild( + void Function(ServiceExtensionResponseBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + ServiceExtensionResponseBuilder toBuilder() => + new ServiceExtensionResponseBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ServiceExtensionResponse && + id == other.id && + resultJson == other.resultJson && + success == other.success && + errorCode == other.errorCode && + errorMessage == other.errorMessage; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, resultJson.hashCode); + _$hash = $jc(_$hash, success.hashCode); + _$hash = $jc(_$hash, errorCode.hashCode); + _$hash = $jc(_$hash, errorMessage.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'ServiceExtensionResponse') + ..add('id', id) + ..add('resultJson', resultJson) + ..add('success', success) + ..add('errorCode', errorCode) + ..add('errorMessage', errorMessage)) + .toString(); + } +} + +class ServiceExtensionResponseBuilder + implements + Builder { + _$ServiceExtensionResponse? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _resultJson; + String? get resultJson => _$this._resultJson; + set resultJson(String? resultJson) => _$this._resultJson = resultJson; + + bool? _success; + bool? get success => _$this._success; + set success(bool? success) => _$this._success = success; + + int? _errorCode; + int? get errorCode => _$this._errorCode; + set errorCode(int? errorCode) => _$this._errorCode = errorCode; + + String? _errorMessage; + String? get errorMessage => _$this._errorMessage; + set errorMessage(String? errorMessage) => _$this._errorMessage = errorMessage; + + ServiceExtensionResponseBuilder(); + + ServiceExtensionResponseBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _resultJson = $v.resultJson; + _success = $v.success; + _errorCode = $v.errorCode; + _errorMessage = $v.errorMessage; + _$v = null; + } + return this; + } + + @override + void replace(ServiceExtensionResponse other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$ServiceExtensionResponse; + } + + @override + void update(void Function(ServiceExtensionResponseBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ServiceExtensionResponse build() => _build(); + + _$ServiceExtensionResponse _build() { + final _$result = + _$v ?? + new _$ServiceExtensionResponse._( + id: BuiltValueNullFieldError.checkNotNull( + id, + r'ServiceExtensionResponse', + 'id', + ), + resultJson: resultJson, + success: BuiltValueNullFieldError.checkNotNull( + success, + r'ServiceExtensionResponse', + 'success', + ), + errorCode: errorCode, + errorMessage: errorMessage, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart index 271331eb3..99fcb9b3e 100644 --- a/dwds/lib/src/connections/debug_connection.dart +++ b/dwds/lib/src/connections/debug_connection.dart @@ -13,7 +13,7 @@ import 'package:vm_service/vm_service.dart'; /// Supports debugging your running application through the Dart VM Service /// Protocol. class DebugConnection { - final AppDebugServices _appDebugServices; + final IAppDebugServices _appDebugServices; final _onDoneCompleter = Completer(); /// Null until [close] is called. @@ -22,9 +22,20 @@ class DebugConnection { Future? _closed; DebugConnection(this._appDebugServices) { - _appDebugServices.chromeProxyService.remoteDebugger.onClose.first.then((_) { - close(); - }); + _setupChromeCloseHandler(); + } + + /// Sets up Chrome remote debugger close handler if available. + void _setupChromeCloseHandler() { + try { + final chromeProxyService = _appDebugServices.chromeProxyService; + if (chromeProxyService != null) { + final remoteDebugger = chromeProxyService.remoteDebugger; + remoteDebugger.onClose.first.then((_) => close()); + } + } catch (_) { + // Chrome proxy service not available in WebSocket-only mode - ignore + } } /// The port of the host Dart VM Service. @@ -41,7 +52,16 @@ class DebugConnection { Future close() => _closed ??= () async { - await _appDebugServices.chromeProxyService.remoteDebugger.close(); + // Close Chrome remote debugger if available + try { + final chromeProxyService = _appDebugServices.chromeProxyService; + if (chromeProxyService != null) { + await chromeProxyService.remoteDebugger.close(); + } + } catch (_) { + // Chrome proxy service not available in WebSocket-only mode - ignore + } + await _appDebugServices.close(); _onDoneCompleter.complete(); }(); diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 329777540..fd12faf4b 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -15,6 +15,7 @@ import 'package:dwds/data/hot_reload_response.dart'; import 'package:dwds/data/isolate_events.dart'; import 'package:dwds/data/register_event.dart'; import 'package:dwds/data/serializers.dart'; +import 'package:dwds/data/service_extension_response.dart'; import 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/connections/app_connection.dart'; import 'package:dwds/src/connections/debug_connection.dart'; @@ -32,7 +33,10 @@ import 'package:dwds/src/servers/extension_debugger.dart'; import 'package:dwds/src/services/app_debug_services.dart'; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; +import 'package:dwds/src/services/web_socket_app_debug_services.dart'; +import 'package:dwds/src/services/web_socket_debug_service.dart'; import 'package:dwds/src/utilities/shared.dart'; +import 'package:dwds/src/web_socket_dwds_vm_client.dart'; import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; import 'package:sse/server/sse_handler.dart'; @@ -56,7 +60,7 @@ class DevHandler { final AssetReader _assetReader; final String _hostname; final _connectedApps = StreamController.broadcast(); - final _servicesByAppId = {}; + final _servicesByAppId = {}; final _appConnectionByAppId = {}; final Stream buildResults; final Future Function() _chromeConnection; @@ -79,6 +83,8 @@ class DevHandler { Stream get connectedApps => _connectedApps.stream; + final bool useWebSocketConnection; + DevHandler( this._chromeConnection, this.buildResults, @@ -93,8 +99,9 @@ class DevHandler { this._injected, this._spawnDds, this._ddsPort, - this._launchDevToolsInNewWindow, - ) { + this._launchDevToolsInNewWindow, { + this.useWebSocketConnection = false, + }) { _subs.add(buildResults.listen(_emitBuildResults)); _listen(); if (_extensionBackend != null) { @@ -239,7 +246,6 @@ class DevHandler { expressionCompiler: _expressionCompiler, spawnDds: _spawnDds, ddsPort: _ddsPort, - sendClientRequest: _sendRequestToClients, ); } @@ -252,31 +258,67 @@ class DevHandler { ); } - Future loadAppServices(AppConnection appConnection) async { + Future loadAppServices(AppConnection appConnection) async { final appId = appConnection.request.appId; var appServices = _servicesByAppId[appId]; if (appServices == null) { - final debugService = await _startLocalDebugService( - await _chromeConnection(), - appConnection, - ); - appServices = await _createAppDebugServices(debugService); - safeUnawaited( - appServices.chromeProxyService.remoteDebugger.onClose.first - .whenComplete(() async { - await appServices?.close(); - _servicesByAppId.remove(appConnection.request.appId); - _logger.info( - 'Stopped debug service on ' - 'ws://${debugService.hostname}:${debugService.port}\n', - ); - }), - ); + if (useWebSocketConnection) { + appServices = await _createWebSocketAppServices(appConnection); + } else { + appServices = await _createChromeAppServices(appConnection); + _setupChromeServiceCleanup(appServices, appConnection); + } _servicesByAppId[appId] = appServices; } + return appServices; } + /// Creates WebSocket-based app services for hot reload functionality. + Future _createWebSocketAppServices( + AppConnection appConnection, + ) async { + final webSocketDebugService = await WebSocketDebugService.start( + 'localhost', + appConnection, + sendClientRequest: _sendRequestToClients, + ); + return _createAppDebugServicesWebSocketMode( + webSocketDebugService, + appConnection, + ); + } + + /// Creates Chrome-based app services for full debugging capabilities. + Future _createChromeAppServices( + AppConnection appConnection, + ) async { + final debugService = await _startLocalDebugService( + await _chromeConnection(), + appConnection, + ); + return _createAppDebugServices(debugService); + } + + /// Sets up cleanup handling for Chrome-based services. + void _setupChromeServiceCleanup( + IAppDebugServices appServices, + AppConnection appConnection, + ) { + safeUnawaited( + appServices.chromeProxyService.remoteDebugger.onClose.first.whenComplete( + () async { + await appServices.close(); + _servicesByAppId.remove(appConnection.request.appId); + _logger.info( + 'Stopped debug service on ' + 'ws://${appServices.debugService.hostname}:${appServices.debugService.port}\n', + ); + }, + ), + ); + } + void _handleConnection(SocketConnection injectedConnection) { _injectedConnections.add(injectedConnection); AppConnection? appConnection; @@ -294,6 +336,7 @@ class DevHandler { appConnection = await _handleConnectRequest( message, injectedConnection, + isWebSocketMode: useWebSocketConnection, ); } else { final connection = appConnection; @@ -302,23 +345,12 @@ class DevHandler { } if (message is DevToolsRequest) { await _handleDebugRequest(connection, injectedConnection); - } else if (message is HotReloadResponse) { - // The app reload operation has completed. Mark the completer as done. - _servicesByAppId[connection.request.appId]?.chromeProxyService - .completeHotReload(message); - } else if (message is IsolateExit) { - _handleIsolateExit(connection); - } else if (message is IsolateStart) { - await _handleIsolateStart(connection); - } else if (message is BatchedDebugEvents) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseBatchedDebugEvents(message); - } else if (message is DebugEvent) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseDebugEvent(message); - } else if (message is RegisterEvent) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseRegisterEvent(message); + } else if (useWebSocketConnection) { + // Handle WebSocket-specific messages + await _handleWebSocketMessage(connection, message); + } else { + // Handle Chrome-specific messages + await _handleChromeMessage(connection, message); } } } catch (e, s) { @@ -356,7 +388,7 @@ class DevHandler { if (services.connectedInstanceId == null || services.connectedInstanceId == connection.request.instanceId) { services.connectedInstanceId = null; - services.chromeProxyService.destroyIsolate(); + _handleIsolateExit(connection); } } } @@ -364,6 +396,64 @@ class DevHandler { ); } + /// Handles WebSocket-specific messages. + Future _handleWebSocketMessage( + AppConnection connection, + Object? message, + ) async { + if (message == null) return; + + if (message is HotReloadResponse) { + _servicesByAppId[connection.request.appId]?.webSocketProxyService + ?.completeHotReload(message); + } else if (message is ServiceExtensionResponse) { + final appId = connection.request.appId; + final wsService = _servicesByAppId[appId]?.webSocketProxyService; + + if (wsService != null) { + wsService.completeServiceExtension(message); + } else { + _logger.warning( + 'No WebSocketProxyService found for appId: $appId to complete service extension', + ); + } + } else { + throw UnsupportedError( + 'Message type ${message.runtimeType} is not supported in WebSocket mode', + ); + } + } + + /// Handles Chrome-specific messages. + Future _handleChromeMessage( + AppConnection connection, + Object? message, + ) async { + if (message == null) return; + + if (message is HotReloadResponse) { + _servicesByAppId[connection.request.appId]?.chromeProxyService + .completeHotReload(message); + } else if (message is IsolateExit) { + _handleIsolateExit(connection); + } else if (message is IsolateStart) { + await _handleIsolateStart(connection); + } else if (message is BatchedDebugEvents) { + _servicesByAppId[connection.request.appId]?.chromeProxyService + .parseBatchedDebugEvents(message); + } else if (message is DebugEvent) { + _servicesByAppId[connection.request.appId]?.chromeProxyService + .parseDebugEvent(message); + } else if (message is RegisterEvent) { + _servicesByAppId[connection.request.appId]?.chromeProxyService + .parseRegisterEvent(message); + } else { + throw UnsupportedError( + 'Message type ${message.runtimeType} is not supported in Chrome mode', + ); + } + } + Future _handleDebugRequest( AppConnection appConnection, SocketConnection sseConnection, @@ -388,7 +478,7 @@ class DevHandler { return; } final debuggerStart = DateTime.now(); - AppDebugServices appServices; + IAppDebugServices appServices; try { appServices = await loadAppServices(appConnection); } catch (_) { @@ -469,10 +559,12 @@ class DevHandler { ); } + /// Handles connection requests for both Chrome and WebSocket modes. Future _handleConnectRequest( ConnectRequest message, - SocketConnection sseConnection, - ) async { + SocketConnection sseConnection, { + required bool isWebSocketMode, + }) async { // After a page refresh, reconnect to the same app services if they // were previously launched and create the new isolate. final services = _servicesByAppId[message.appId]; @@ -498,27 +590,15 @@ class DevHandler { existingConnection?.isInKeepAlivePeriod == true); if (canReuseConnection) { - // Disconnect any old connection (eg. those in the keep-alive waiting - // state when reloading the page). - existingConnection?.shutDown(); - services.chromeProxyService.destroyIsolate(); - // Reconnect to existing service. - services.connectedInstanceId = message.instanceId; - - if (services.chromeProxyService.pauseIsolatesOnStart) { - // If the pause-isolates-on-start flag is set, we need to wait for - // the resume event to run the app's main() method. - _waitForResumeEventToRunMain( - services.chromeProxyService.resumeAfterRestartEventsStream, - readyToRunMainCompleter, - ); - } else { - // Otherwise, we can run the app's main() method immediately. - readyToRunMainCompleter.complete(); - } - - await services.chromeProxyService.createIsolate(connection); + await _reconnectToService( + services, + existingConnection, + connection, + message, + readyToRunMainCompleter, + isWebSocketMode, + ); } else { // If this is the initial app connection, we can run the app's main() // method immediately. @@ -526,9 +606,59 @@ class DevHandler { } _appConnectionByAppId[message.appId] = connection; _connectedApps.add(connection); + _logger.fine('App connection established for: ${message.appId}'); return connection; } + /// Handles reconnection to existing services. + Future _reconnectToService( + IAppDebugServices services, + AppConnection? existingConnection, + AppConnection newConnection, + ConnectRequest message, + Completer readyToRunMainCompleter, + bool isWebSocketMode, + ) async { + // Disconnect old connection + existingConnection?.shutDown(); + services.connectedInstanceId = message.instanceId; + + if (isWebSocketMode) { + services.webSocketProxyService?.destroyIsolate(); + _logger.finest('WebSocket service reconnected for app: ${message.appId}'); + + _setupMainExecution( + services.webSocketProxyService?.pauseIsolatesOnStart == true, + services.webSocketProxyService?.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } else { + services.chromeProxyService.destroyIsolate(); + _logger.finest('Chrome service reconnected for app: ${message.appId}'); + + _setupMainExecution( + services.chromeProxyService.pauseIsolatesOnStart, + services.chromeProxyService.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } + + await _handleIsolateStart(newConnection); + } + + /// Sets up main() execution timing based on pause settings. + void _setupMainExecution( + bool pauseIsolatesOnStart, + Stream? resumeEventsStream, + Completer readyToRunMainCompleter, + ) { + if (pauseIsolatesOnStart && resumeEventsStream != null) { + _waitForResumeEventToRunMain(resumeEventsStream, readyToRunMainCompleter); + } else { + readyToRunMainCompleter.complete(); + } + } + /// Waits for a resume event to trigger the app's main() method. /// /// The [readyToRunMainCompleter]'s future will be passed to the @@ -545,14 +675,30 @@ class DevHandler { }); } + /// Handles isolate exit events for both WebSocket and Chrome-based debugging. void _handleIsolateExit(AppConnection appConnection) { - _servicesByAppId[appConnection.request.appId]?.chromeProxyService - .destroyIsolate(); + final appId = appConnection.request.appId; + + if (useWebSocketConnection) { + _servicesByAppId[appId]?.webSocketProxyService?.destroyIsolate(); + } else { + _servicesByAppId[appId]?.chromeProxyService.destroyIsolate(); + } } + /// Handles isolate start events for both WebSocket and Chrome-based debugging. Future _handleIsolateStart(AppConnection appConnection) async { - await _servicesByAppId[appConnection.request.appId]?.chromeProxyService - .createIsolate(appConnection); + final appId = appConnection.request.appId; + + if (useWebSocketConnection) { + await _servicesByAppId[appId]?.webSocketProxyService?.createIsolate( + appConnection, + ); + } else { + await _servicesByAppId[appId]?.chromeProxyService.createIsolate( + appConnection, + ); + } } void _listen() { @@ -580,7 +726,7 @@ class DevHandler { ); } - Future _createAppDebugServices( + Future _createAppDebugServices( DebugService debugService, ) async { final dwdsStats = DwdsStats(); @@ -614,6 +760,28 @@ class DevHandler { return appDebugService; } + Future _createAppDebugServicesWebSocketMode( + WebSocketDebugService webSocketDebugService, + AppConnection appConnection, + ) async { + final wsVmClient = await WebSocketDwdsVmClient.create( + webSocketDebugService, + ); + final wsAppDebugService = WebSocketAppDebugServices( + webSocketDebugService, + wsVmClient, + ); + + safeUnawaited(_handleIsolateStart(appConnection)); + final debugServiceUri = webSocketDebugService.uri; + _logger.info('Debug service listening on $debugServiceUri\n'); + + // Notify that DWDS has been launched and a debug connection has been made: + _maybeEmitDwdsLaunchEvent(); + + return wsAppDebugService; + } + Future _listenForDebugExtension() async { final extensionBackend = _extensionBackend; if (extensionBackend == null) { @@ -634,6 +802,7 @@ class DevHandler { extensionDebugger.devToolsRequestStream.listen((devToolsRequest) async { try { await _handleDevToolsRequest(extensionDebugger, devToolsRequest); + _logger.finest('DevTools request processed successfully'); } catch (error) { _logger.severe('Encountered error handling DevTools request.'); extensionDebugger.closeWithError(error); @@ -688,7 +857,6 @@ class DevHandler { expressionCompiler: _expressionCompiler, spawnDds: _spawnDds, ddsPort: _ddsPort, - sendClientRequest: _sendRequestToClients, ); appServices = await _createAppDebugServices(debugService); extensionDebugger.sendEvent('dwds.debugUri', debugService.uri); diff --git a/dwds/lib/src/handlers/injector.dart b/dwds/lib/src/handlers/injector.dart index d55494944..121c3628a 100644 --- a/dwds/lib/src/handlers/injector.dart +++ b/dwds/lib/src/handlers/injector.dart @@ -29,22 +29,12 @@ const _clientScript = 'dwds/src/injected/client'; /// This class is responsible for modifying the served JavaScript files /// to include the injected DWDS client, enabling debugging capabilities /// and source mapping when running in a browser environment. -/// -/// TODO(yjessy): Remove this when the DWDS WebSocket connection is implemented. -/// The `_useDwdsWebSocketConnection` flag determines the communication protocol: -/// - When `true`, uses a socket-based implementation. -/// - When `false`, uses Chrome-based communication protocol. class DwdsInjector { final Future? _extensionUri; final _devHandlerPaths = StreamController(); final _logger = Logger('DwdsInjector'); - final bool _useDwdsWebSocketConnection; - DwdsInjector({ - Future? extensionUri, - bool useDwdsWebSocketConnection = false, - }) : _extensionUri = extensionUri, - _useDwdsWebSocketConnection = useDwdsWebSocketConnection; + DwdsInjector({Future? extensionUri}) : _extensionUri = extensionUri; /// Returns the embedded dev handler paths. /// @@ -111,7 +101,6 @@ class DwdsInjector { devHandlerPath, entrypoint, await _extensionUri, - _useDwdsWebSocketConnection, ); body += await globalToolConfiguration.loadStrategy.bootstrapFor( entrypoint, @@ -148,7 +137,6 @@ Future _injectClientAndHoistMain( String devHandlerPath, String entrypointPath, String? extensionUri, - bool useDwdsWebSocketConnection, ) async { final bodyLines = body.split('\n'); final extensionIndex = bodyLines.indexWhere( @@ -167,7 +155,6 @@ Future _injectClientAndHoistMain( devHandlerPath, entrypointPath, extensionUri, - useDwdsWebSocketConnection, ); result += ''' // Injected by dwds for debugging support. @@ -199,7 +186,6 @@ Future _injectedClientSnippet( String devHandlerPath, String entrypointPath, String? extensionUri, - bool useDwdsWebSocketConnection, ) async { final loadStrategy = globalToolConfiguration.loadStrategy; final buildSettings = loadStrategy.buildSettings; @@ -218,7 +204,6 @@ Future _injectedClientSnippet( 'window.\$dartEmitDebugEvents = ${debugSettings.emitDebugEvents};\n' 'window.\$isInternalBuild = ${appMetadata.isInternalBuild};\n' 'window.\$isFlutterApp = ${buildSettings.isFlutterApp};\n' - 'window.\$useDwdsWebSocketConnection = $useDwdsWebSocketConnection;\n' '${loadStrategy is DdcLibraryBundleStrategy ? 'window.\$hotReloadSourcesPath = "${loadStrategy.hotReloadSourcesUri.toString()}";\n' : ''}' '${loadStrategy.loadClientSnippet(_clientScript)}'; diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index 40bf2fd5d..8ff09194c 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -8471,6 +8471,48 @@ }, _$serializers_closure0: function _$serializers_closure0() { }, + ServiceExtensionRequest: function ServiceExtensionRequest() { + }, + _$ServiceExtensionRequestSerializer: function _$ServiceExtensionRequestSerializer() { + }, + _$ServiceExtensionRequest: function _$ServiceExtensionRequest(t0, t1, t2) { + this.id = t0; + this.method = t1; + this.argsJson = t2; + }, + ServiceExtensionRequestBuilder: function ServiceExtensionRequestBuilder() { + var _ = this; + _._argsJson = _._service_extension_request$_method = _._service_extension_request$_id = _._service_extension_request$_$v = null; + }, + ServiceExtensionResponse_ServiceExtensionResponse$fromResult(errorCode, errorMessage, id, result, success) { + var t1 = new A.ServiceExtensionResponseBuilder(); + type$.nullable_void_Function_ServiceExtensionResponseBuilder._as(new A.ServiceExtensionResponse_ServiceExtensionResponse$fromResult_closure(id, success, result, errorCode, errorMessage)).call$1(t1); + return t1._service_extension_response$_build$0(); + }, + ServiceExtensionResponse: function ServiceExtensionResponse() { + }, + ServiceExtensionResponse_ServiceExtensionResponse$fromResult_closure: function ServiceExtensionResponse_ServiceExtensionResponse$fromResult_closure(t0, t1, t2, t3, t4) { + var _ = this; + _.id = t0; + _.success = t1; + _.result = t2; + _.errorCode = t3; + _.errorMessage = t4; + }, + _$ServiceExtensionResponseSerializer: function _$ServiceExtensionResponseSerializer() { + }, + _$ServiceExtensionResponse: function _$ServiceExtensionResponse(t0, t1, t2, t3, t4) { + var _ = this; + _.id = t0; + _.resultJson = t1; + _.success = t2; + _.errorCode = t3; + _.errorMessage = t4; + }, + ServiceExtensionResponseBuilder: function ServiceExtensionResponseBuilder() { + var _ = this; + _._service_extension_response$_errorMessage = _._errorCode = _._service_extension_response$_success = _._resultJson = _._service_extension_response$_id = _._service_extension_response$_$v = null; + }, BatchedStreamController: function BatchedStreamController(t0, t1, t2, t3, t4, t5) { var _ = this; _._checkDelayMilliseconds = t0; @@ -9674,6 +9716,9 @@ _sendResponse(clientSink, builder, requestId, errorMessage, success, $T) { A._trySendEvent(clientSink, B.C_JsonCodec.encode$2$toEncodable($.$get$serializers().serialize$1(builder.call$1(new A._sendResponse_closure(requestId, success, errorMessage))), null), type$.dynamic); }, + _sendServiceExtensionResponse(clientSink, requestId, errorCode, errorMessage, result, success) { + A._trySendEvent(clientSink, B.C_JsonCodec.encode$2$toEncodable($.$get$serializers().serialize$1(A.ServiceExtensionResponse_ServiceExtensionResponse$fromResult(errorCode, errorMessage, requestId, result, success)), null), type$.dynamic); + }, handleWebSocketHotReloadRequest($event, manager, clientSink) { return A.handleWebSocketHotReloadRequest$body($event, manager, clientSink); }, @@ -9731,6 +9776,64 @@ }); return A._asyncStartSync($async$handleWebSocketHotReloadRequest, $async$completer); }, + handleServiceExtensionRequest(request, clientSink, manager) { + return A.handleServiceExtensionRequest$body(request, clientSink, manager); + }, + handleServiceExtensionRequest$body(request, clientSink, manager) { + var $async$goto = 0, + $async$completer = A._makeAsyncAwaitCompleter(type$.void), + $async$handler = 1, $async$errorStack = [], result, e, t1, exception, $async$exception; + var $async$handleServiceExtensionRequest = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { + if ($async$errorCode === 1) { + $async$errorStack.push($async$result); + $async$goto = $async$handler; + } + while (true) + switch ($async$goto) { + case 0: + // Function start + $async$handler = 3; + t1 = request.argsJson; + t1 = t1.length === 0 ? A.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.dynamic) : type$.Map_String_dynamic._as(B.C_JsonCodec.decode$1(t1)); + $async$goto = 6; + return A._asyncAwait(manager.handleServiceExtension$2(request.method, t1), $async$handleServiceExtensionRequest); + case 6: + // returning from await. + result = $async$result; + t1 = request.id; + if (result != null) + A._sendServiceExtensionResponse(clientSink, t1, null, null, result, true); + else + A._sendServiceExtensionResponse(clientSink, t1, -32601, "Service extension not supported", null, false); + $async$handler = 1; + // goto after finally + $async$goto = 5; + break; + case 3: + // catch + $async$handler = 2; + $async$exception = $async$errorStack.pop(); + e = A.unwrapException($async$exception); + A._sendServiceExtensionResponse(clientSink, request.id, null, J.toString$0$(e), null, false); + // goto after finally + $async$goto = 5; + break; + case 2: + // uncaught + // goto rethrow + $async$goto = 1; + break; + case 5: + // after finally + // implicit return + return A._asyncReturn(null, $async$completer); + case 1: + // rethrow + return A._asyncRethrow($async$errorStack.at(-1), $async$completer); + } + }); + return A._asyncStartSync($async$handleServiceExtensionRequest, $async$completer); + }, hotReloadSourcesPath() { var path = A._asStringQ(init.G.$hotReloadSourcesPath); if (path == null) @@ -9847,6 +9950,37 @@ }); return A._asyncStartSync($async$_Debugger_maybeInvokeFlutterDisassemble, $async$completer); }, + _Debugger_maybeInvokeFlutterReassemble(_this) { + return A._Debugger_maybeInvokeFlutterReassemble$body(_this); + }, + _Debugger_maybeInvokeFlutterReassemble$body(_this) { + var $async$goto = 0, + $async$completer = A._makeAsyncAwaitCompleter(type$.void), + t1; + var $async$_Debugger_maybeInvokeFlutterReassemble = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { + if ($async$errorCode === 1) + return A._asyncRethrow($async$result, $async$completer); + while (true) + switch ($async$goto) { + case 0: + // Function start + t1 = type$.JSArray_nullable_Object._as(_this.extensionNames); + $async$goto = J.contains$1$asx(type$.List_String._is(t1) ? t1 : new A.CastList(t1, A._arrayInstanceType(t1)._eval$1("CastList<1,String>")), "ext.flutter.reassemble") ? 2 : 3; + break; + case 2: + // then + $async$goto = 4; + return A._asyncAwait(A.promiseToFuture(type$.JSObject._as(_this.invokeExtension("ext.flutter.reassemble", "{}")), type$.String), $async$_Debugger_maybeInvokeFlutterReassemble); + case 4: + // returning from await. + case 3: + // join + // implicit return + return A._asyncReturn(null, $async$completer); + } + }); + return A._asyncStartSync($async$_Debugger_maybeInvokeFlutterReassemble, $async$completer); + }, DdcLibraryBundleRestarter: function DdcLibraryBundleRestarter() { this.__DdcLibraryBundleRestarter__sourcesAndLibrariesToReload_A = $; }, @@ -11127,6 +11261,10 @@ get$isEmpty(_) { var t1 = this.__internal$_source; return t1.get$isEmpty(t1); + }, + get$isNotEmpty(_) { + var t1 = this.__internal$_source; + return t1.get$isNotEmpty(t1); } }; A.CastMap_forEach_closure.prototype = { @@ -11160,7 +11298,7 @@ call$0() { return A.Future_Future$value(null, type$.void); }, - $signature: 19 + $signature: 16 }; A.SentinelValue.prototype = {}; A.EfficientLengthIterable.prototype = {}; @@ -11647,6 +11785,9 @@ get$isEmpty(_) { return this.get$length(this) === 0; }, + get$isNotEmpty(_) { + return this.get$length(this) !== 0; + }, toString$0(_) { return A.MapBase_mapToString(this); }, @@ -11898,6 +12039,9 @@ get$isEmpty(_) { return this.__js_helper$_length === 0; }, + get$isNotEmpty(_) { + return this.__js_helper$_length !== 0; + }, get$keys() { return new A.LinkedHashMapKeysIterable(this, A._instanceType(this)._eval$1("LinkedHashMapKeysIterable<1>")); }, @@ -12261,13 +12405,13 @@ call$2(o, tag) { return this.getUnknownTag(o, tag); }, - $signature: 66 + $signature: 35 }; A.initHooks_closure1.prototype = { call$1(tag) { return this.prototypeForTag(A._asString(tag)); }, - $signature: 90 + $signature: 49 }; A._Record.prototype = { get$runtimeType(_) { @@ -12855,7 +12999,7 @@ t2 = this.span; t1.firstChild ? t1.removeChild(t2) : t1.appendChild(t2); }, - $signature: 61 + $signature: 37 }; A._AsyncRun__scheduleImmediateJsOverride_internalCallback.prototype = { call$0() { @@ -12961,13 +13105,13 @@ call$2(error, stackTrace) { this.bodyFunction.call$2(1, new A.ExceptionAndStackTrace(error, type$.StackTrace._as(stackTrace))); }, - $signature: 52 + $signature: 47 }; A._wrapJsFunctionForAsync_closure.prototype = { call$2(errorCode, result) { this.$protected(A._asInt(errorCode), result); }, - $signature: 37 + $signature: 57 }; A.AsyncError.prototype = { toString$0(_) { @@ -14778,7 +14922,7 @@ t2._processUncaughtError$3(zone, type$.Object._as(e), t1._as(s)); } }, - $signature: 63 + $signature: 39 }; A._HashMap.prototype = { get$length(_) { @@ -14787,6 +14931,9 @@ get$isEmpty(_) { return this._collection$_length === 0; }, + get$isNotEmpty(_) { + return this._collection$_length !== 0; + }, get$keys() { return new A._HashMapKeyIterable(this, A._instanceType(this)._eval$1("_HashMapKeyIterable<1>")); }, @@ -15000,7 +15147,7 @@ call$1(v) { return this.K._is(v); }, - $signature: 12 + $signature: 14 }; A._HashMapKeyIterable.prototype = { get$length(_) { @@ -15081,7 +15228,7 @@ call$1(v) { return this.K._is(v); }, - $signature: 12 + $signature: 14 }; A._HashSet.prototype = { get$iterator(_) { @@ -15449,7 +15596,7 @@ call$2(k, v) { this.result.$indexSet(0, this.K._as(k), this.V._as(v)); }, - $signature: 23 + $signature: 29 }; A.ListBase.prototype = { get$iterator(receiver) { @@ -15625,6 +15772,10 @@ var t1 = this.get$keys(); return t1.get$isEmpty(t1); }, + get$isNotEmpty(_) { + var t1 = this.get$keys(); + return t1.get$isNotEmpty(t1); + }, toString$0(_) { return A.MapBase_mapToString(this); }, @@ -15643,7 +15794,7 @@ t2 = A.S(v); t1._contents += t2; }, - $signature: 24 + $signature: 31 }; A._UnmodifiableMapMixin.prototype = { $indexSet(_, key, value) { @@ -15674,6 +15825,10 @@ var t1 = this._collection$_map; return t1.get$isEmpty(t1); }, + get$isNotEmpty(_) { + var t1 = this._collection$_map; + return t1.get$isNotEmpty(t1); + }, get$length(_) { var t1 = this._collection$_map; return t1.get$length(t1); @@ -16178,6 +16333,9 @@ get$isEmpty(_) { return this.get$length(0) === 0; }, + get$isNotEmpty(_) { + return this.get$length(0) > 0; + }, get$keys() { if (this._processed == null) { var t1 = this._data; @@ -16296,7 +16454,7 @@ } return null; }, - $signature: 25 + $signature: 21 }; A._Utf8Decoder__decoderNonfatal_closure.prototype = { call$0() { @@ -16308,7 +16466,7 @@ } return null; }, - $signature: 25 + $signature: 21 }; A.AsciiCodec.prototype = { encode$1(source) { @@ -16599,6 +16757,9 @@ var t1 = A._JsonStringStringifier_stringify(value, this.get$encoder()._toEncodable, null); return t1; }, + encode$1(value) { + return this.encode$2$toEncodable(value, null); + }, get$encoder() { return B.JsonEncoder_null; }, @@ -16811,7 +16972,7 @@ B.JSArray_methods.$indexSet(t1, t2.i++, key); B.JSArray_methods.$indexSet(t1, t2.i++, value); }, - $signature: 24 + $signature: 31 }; A._JsonStringStringifier.prototype = { get$_partialResult() { @@ -17493,7 +17654,7 @@ hash = hash + ((hash & 524287) << 10) & 536870911; return hash ^ hash >>> 6; }, - $signature: 26 + $signature: 27 }; A._BigIntImpl_hashCode_finish.prototype = { call$1(hash) { @@ -17501,7 +17662,7 @@ hash ^= hash >>> 11; return hash + ((hash & 16383) << 15) & 536870911; }, - $signature: 27 + $signature: 22 }; A.DateTime.prototype = { $eq(_, other) { @@ -17920,13 +18081,13 @@ call$2(msg, position) { throw A.wrapException(A.FormatException$("Illegal IPv4 address, " + msg, this.host, position)); }, - $signature: 56 + $signature: 38 }; A.Uri_parseIPv6Address_error.prototype = { call$2(msg, position) { throw A.wrapException(A.FormatException$("Illegal IPv6 address, " + msg, this.host, position)); }, - $signature: 53 + $signature: 43 }; A.Uri_parseIPv6Address_parseHex.prototype = { call$2(start, end) { @@ -17938,7 +18099,7 @@ this.error.call$2("each part must be in the range of `0x0..0xFFFF`", start); return value; }, - $signature: 26 + $signature: 27 }; A._Uri.prototype = { get$_text() { @@ -18239,7 +18400,7 @@ call$1(s) { return A._Uri__uriEncode(64, A._asString(s), B.C_Utf8Codec, false); }, - $signature: 13 + $signature: 15 }; A.UriData.prototype = { get$uri() { @@ -18565,7 +18726,7 @@ var t1 = type$.JavaScriptFunction; this._this.then$1$2$onError(new A.FutureOfJSAnyToJSPromise_get_toJS__closure(t1._as(resolve)), new A.FutureOfJSAnyToJSPromise_get_toJS__closure0(t1._as(reject)), type$.nullable_Object); }, - $signature: 29 + $signature: 24 }; A.FutureOfJSAnyToJSPromise_get_toJS__closure.prototype = { call$1(value) { @@ -18591,21 +18752,21 @@ t1.call(t1, wrapper); return wrapper; }, - $signature: 50 + $signature: 51 }; A.FutureOfVoidToJSPromise_get_toJS_closure.prototype = { call$2(resolve, reject) { var t1 = type$.JavaScriptFunction; this._this.then$1$2$onError(new A.FutureOfVoidToJSPromise_get_toJS__closure(t1._as(resolve)), new A.FutureOfVoidToJSPromise_get_toJS__closure0(t1._as(reject)), type$.nullable_Object); }, - $signature: 29 + $signature: 24 }; A.FutureOfVoidToJSPromise_get_toJS__closure.prototype = { call$1(__wc0_formal) { var t1 = this.resolve; return t1.call(t1); }, - $signature: 46 + $signature: 53 }; A.FutureOfVoidToJSPromise_get_toJS__closure0.prototype = { call$2(error, stackTrace) { @@ -18925,7 +19086,7 @@ call$2(h, i) { return A._combine(A._asInt(h), J.get$hashCode$(i)); }, - $signature: 42 + $signature: 62 }; A.BuiltList.prototype = { toBuilder$0() { @@ -19478,7 +19639,7 @@ var t1 = this.$this.$ti; this.replacement.$indexSet(0, t1._precomputed1._as(key), t1._rest[1]._as(value)); }, - $signature: 23 + $signature: 29 }; A.BuiltSet.prototype = { get$hashCode(_) { @@ -19859,7 +20020,7 @@ $._indentingBuiltValueToStringHelperIndent = $._indentingBuiltValueToStringHelperIndent + 2; return new A.IndentingBuiltValueToStringHelper(t1); }, - $signature: 39 + $signature: 64 }; A.IndentingBuiltValueToStringHelper.prototype = { add$2(_, field, value) { @@ -19992,34 +20153,34 @@ call$0() { return A.ListBuilder_ListBuilder(B.List_empty0, type$.Object); }, - $signature: 38 + $signature: 67 }; A.Serializers_Serializers_closure0.prototype = { call$0() { var t1 = type$.Object; return A.ListMultimapBuilder_ListMultimapBuilder(t1, t1); }, - $signature: 36 + $signature: 90 }; A.Serializers_Serializers_closure1.prototype = { call$0() { var t1 = type$.Object; return A.MapBuilder_MapBuilder(t1, t1); }, - $signature: 34 + $signature: 91 }; A.Serializers_Serializers_closure2.prototype = { call$0() { return A.SetBuilder_SetBuilder(type$.Object); }, - $signature: 35 + $signature: 107 }; A.Serializers_Serializers_closure3.prototype = { call$0() { var t1 = type$.Object; return A.SetMultimapBuilder_SetMultimapBuilder(t1, t1); }, - $signature: 89 + $signature: 36 }; A.FullType.prototype = { $eq(_, other) { @@ -21143,6 +21304,9 @@ get$isEmpty(_) { return this._base.__js_helper$_length === 0; }, + get$isNotEmpty(_) { + return this._base.__js_helper$_length !== 0; + }, get$keys() { var t1 = this._base, t2 = A._instanceType(t1)._eval$1("LinkedHashMapValuesIterable<2>"), @@ -23519,95 +23683,357 @@ }, $signature: 41 }; - A.BatchedStreamController.prototype = { - _batchAndSendEvents$0() { - var $async$goto = 0, - $async$completer = A._makeAsyncAwaitCompleter(type$.void), - $async$self = this, t2, t3, t4, t5, t6, t7, t8, lastSendTime0, lastEvent, duration, t1, buffer, lastSendTime, $async$temp1, $async$temp2; - var $async$_batchAndSendEvents$0 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { - if ($async$errorCode === 1) - return A._asyncRethrow($async$result, $async$completer); - while (true) - switch ($async$goto) { - case 0: - // Function start - duration = A.Duration$(0, $async$self._checkDelayMilliseconds); - t1 = $async$self.$ti; - buffer = A._setArrayType([], t1._eval$1("JSArray<1>")); - lastSendTime = Date.now(); - t2 = $async$self._batchDelayMilliseconds, t3 = $async$self._outputController, t4 = A._instanceType(t3), t1 = t1._precomputed1, t5 = t4._precomputed1, t4 = t4._eval$1("_DelayedData<1>"); - case 2: - // for condition - $async$goto = 4; - return A._asyncAwait($async$self._hasEventOrTimeOut$1(duration), $async$_batchAndSendEvents$0); - case 4: - // returning from await. - if (!$async$result) { - // goto after for - $async$goto = 3; - break; - } - $async$goto = 7; - return A._asyncAwait($async$self._hasEventDuring$1(duration), $async$_batchAndSendEvents$0); - case 7: - // returning from await. - $async$goto = $async$result ? 5 : 6; - break; - case 5: - // then - t6 = $async$self.__BatchedStreamController__inputQueue_A; - t6 === $ && A.throwLateFieldNI("_inputQueue"); - t7 = t6.$ti; - t8 = new A._Future($.Zone__current, t7._eval$1("_Future<1>")); - t6._addRequest$1(new A._NextRequest(new A._AsyncCompleter(t8, t7._eval$1("_AsyncCompleter<1>")), t7._eval$1("_NextRequest<1>"))); - $async$temp1 = B.JSArray_methods; - $async$temp2 = buffer; - $async$goto = 8; - return A._asyncAwait(t8, $async$_batchAndSendEvents$0); - case 8: - // returning from await. - $async$temp1.add$1($async$temp2, $async$result); - case 6: - // join - lastSendTime0 = Date.now(); - if (lastSendTime0 > lastSendTime + t2) { - if (buffer.length !== 0) { - t6 = A.List_List$_of(buffer, t1); - t5._as(t6); - t7 = t3._state; - if (t7 >= 4) - A.throwExpression(t3._badEventState$0()); - if ((t7 & 1) !== 0) - t3._sendData$1(t6); - else if ((t7 & 3) === 0) { - t7 = t3._ensurePendingEvents$0(); - t6 = new A._DelayedData(t6, t4); - lastEvent = t7.lastPendingEvent; - if (lastEvent == null) - t7.firstPendingEvent = t7.lastPendingEvent = t6; - else { - lastEvent.set$next(t6); - t7.lastPendingEvent = t6; - } - } - B.JSArray_methods.clear$0(buffer); - } - lastSendTime = lastSendTime0; - } - // goto for condition - $async$goto = 2; - break; - case 3: - // after for - if (buffer.length !== 0) { - t1 = A.List_List$_of(buffer, t1); - t3.add$1(0, t5._as(t1)); - } - $async$self._completer.complete$1(true); - // implicit return - return A._asyncReturn(null, $async$completer); - } - }); + A.ServiceExtensionRequest.prototype = {}; + A._$ServiceExtensionRequestSerializer.prototype = { + serialize$3$specifiedType(serializers, object, specifiedType) { + type$.ServiceExtensionRequest._as(object); + return ["id", serializers.serialize$2$specifiedType(object.id, B.FullType_PT1), "method", serializers.serialize$2$specifiedType(object.method, B.FullType_PT1), "argsJson", serializers.serialize$2$specifiedType(object.argsJson, B.FullType_PT1)]; + }, + serialize$2(serializers, object) { + return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); + }, + deserialize$3$specifiedType(serializers, serialized, specifiedType) { + var t1, value, _$result, t2, t3, t4, + _s8_ = "argsJson", + _s23_ = "ServiceExtensionRequest", + result = new A.ServiceExtensionRequestBuilder(), + iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); + for (; iterator.moveNext$0();) { + t1 = iterator.get$current(); + t1.toString; + A._asString(t1); + iterator.moveNext$0(); + value = iterator.get$current(); + switch (t1) { + case "id": + t1 = serializers.deserialize$2$specifiedType(value, B.FullType_PT1); + t1.toString; + A._asString(t1); + result.get$_service_extension_request$_$this()._service_extension_request$_id = t1; + break; + case "method": + t1 = serializers.deserialize$2$specifiedType(value, B.FullType_PT1); + t1.toString; + A._asString(t1); + result.get$_service_extension_request$_$this()._service_extension_request$_method = t1; + break; + case "argsJson": + t1 = serializers.deserialize$2$specifiedType(value, B.FullType_PT1); + t1.toString; + A._asString(t1); + result.get$_service_extension_request$_$this()._argsJson = t1; + break; + } + } + _$result = result._service_extension_request$_$v; + if (_$result == null) { + t1 = type$.String; + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_service_extension_request$_$this()._service_extension_request$_id, _s23_, "id", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(result.get$_service_extension_request$_$this()._service_extension_request$_method, _s23_, "method", t1); + t4 = A.BuiltValueNullFieldError_checkNotNull(result.get$_service_extension_request$_$this()._argsJson, _s23_, _s8_, t1); + _$result = new A._$ServiceExtensionRequest(t2, t3, t4); + A.BuiltValueNullFieldError_checkNotNull(t2, _s23_, "id", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s23_, "method", t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s23_, _s8_, t1); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.ServiceExtensionRequest); + return result._service_extension_request$_$v = _$result; + }, + deserialize$2(serializers, serialized) { + return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); + }, + $isSerializer: 1, + $isStructuredSerializer: 1, + get$types() { + return B.List_4i4; + }, + get$wireName() { + return "ServiceExtensionRequest"; + } + }; + A._$ServiceExtensionRequest.prototype = { + $eq(_, other) { + var _this = this; + if (other == null) + return false; + if (other === _this) + return true; + return other instanceof A._$ServiceExtensionRequest && _this.id === other.id && _this.method === other.method && _this.argsJson === other.argsJson; + }, + get$hashCode(_) { + return A.$jf(A.$jc(A.$jc(A.$jc(0, B.JSString_methods.get$hashCode(this.id)), B.JSString_methods.get$hashCode(this.method)), B.JSString_methods.get$hashCode(this.argsJson))); + }, + toString$0(_) { + var t1 = $.$get$newBuiltValueToStringHelper().call$1("ServiceExtensionRequest"), + t2 = J.getInterceptor$ax(t1); + t2.add$2(t1, "id", this.id); + t2.add$2(t1, "method", this.method); + t2.add$2(t1, "argsJson", this.argsJson); + return t2.toString$0(t1); + } + }; + A.ServiceExtensionRequestBuilder.prototype = { + set$id(id) { + this.get$_service_extension_request$_$this()._service_extension_request$_id = id; + }, + get$_service_extension_request$_$this() { + var _this = this, + $$v = _this._service_extension_request$_$v; + if ($$v != null) { + _this._service_extension_request$_id = $$v.id; + _this._service_extension_request$_method = $$v.method; + _this._argsJson = $$v.argsJson; + _this._service_extension_request$_$v = null; + } + return _this; + } + }; + A.ServiceExtensionResponse.prototype = {}; + A.ServiceExtensionResponse_ServiceExtensionResponse$fromResult_closure.prototype = { + call$1(b) { + var t1, _this = this; + b.get$_service_extension_response$_$this()._service_extension_response$_id = _this.id; + b.get$_service_extension_response$_$this()._service_extension_response$_success = _this.success; + t1 = _this.result; + t1 = t1 != null ? B.C_JsonCodec.encode$1(t1) : null; + b.get$_service_extension_response$_$this()._resultJson = t1; + b.get$_service_extension_response$_$this()._errorCode = _this.errorCode; + b.get$_service_extension_response$_$this()._service_extension_response$_errorMessage = _this.errorMessage; + return b; + }, + $signature: 42 + }; + A._$ServiceExtensionResponseSerializer.prototype = { + serialize$3$specifiedType(serializers, object, specifiedType) { + var result, value; + type$.ServiceExtensionResponse._as(object); + result = ["id", serializers.serialize$2$specifiedType(object.id, B.FullType_PT1), "success", serializers.serialize$2$specifiedType(object.success, B.FullType_R6B)]; + value = object.resultJson; + if (value != null) { + result.push("resultJson"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_PT1)); + } + value = object.errorCode; + if (value != null) { + result.push("errorCode"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_rTD)); + } + value = object.errorMessage; + if (value != null) { + result.push("errorMessage"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_PT1)); + } + return result; + }, + serialize$2(serializers, object) { + return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); + }, + deserialize$3$specifiedType(serializers, serialized, specifiedType) { + var t1, value, + result = new A.ServiceExtensionResponseBuilder(), + iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); + for (; iterator.moveNext$0();) { + t1 = iterator.get$current(); + t1.toString; + A._asString(t1); + iterator.moveNext$0(); + value = iterator.get$current(); + switch (t1) { + case "id": + t1 = serializers.deserialize$2$specifiedType(value, B.FullType_PT1); + t1.toString; + A._asString(t1); + result.get$_service_extension_response$_$this()._service_extension_response$_id = t1; + break; + case "resultJson": + t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_PT1)); + result.get$_service_extension_response$_$this()._resultJson = t1; + break; + case "success": + t1 = serializers.deserialize$2$specifiedType(value, B.FullType_R6B); + t1.toString; + A._asBool(t1); + result.get$_service_extension_response$_$this()._service_extension_response$_success = t1; + break; + case "errorCode": + t1 = A._asIntQ(serializers.deserialize$2$specifiedType(value, B.FullType_rTD)); + result.get$_service_extension_response$_$this()._errorCode = t1; + break; + case "errorMessage": + t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_PT1)); + result.get$_service_extension_response$_$this()._service_extension_response$_errorMessage = t1; + break; + } + } + return result._service_extension_response$_build$0(); + }, + deserialize$2(serializers, serialized) { + return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); + }, + $isSerializer: 1, + $isStructuredSerializer: 1, + get$types() { + return B.List_5rA; + }, + get$wireName() { + return "ServiceExtensionResponse"; + } + }; + A._$ServiceExtensionResponse.prototype = { + $eq(_, other) { + var _this = this; + if (other == null) + return false; + if (other === _this) + return true; + return other instanceof A._$ServiceExtensionResponse && _this.id === other.id && _this.resultJson == other.resultJson && _this.success === other.success && _this.errorCode == other.errorCode && _this.errorMessage == other.errorMessage; + }, + get$hashCode(_) { + var _this = this; + return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, B.JSString_methods.get$hashCode(_this.id)), J.get$hashCode$(_this.resultJson)), B.JSBool_methods.get$hashCode(_this.success)), J.get$hashCode$(_this.errorCode)), J.get$hashCode$(_this.errorMessage))); + }, + toString$0(_) { + var _this = this, + t1 = $.$get$newBuiltValueToStringHelper().call$1("ServiceExtensionResponse"), + t2 = J.getInterceptor$ax(t1); + t2.add$2(t1, "id", _this.id); + t2.add$2(t1, "resultJson", _this.resultJson); + t2.add$2(t1, "success", _this.success); + t2.add$2(t1, "errorCode", _this.errorCode); + t2.add$2(t1, "errorMessage", _this.errorMessage); + return t2.toString$0(t1); + } + }; + A.ServiceExtensionResponseBuilder.prototype = { + set$id(id) { + this.get$_service_extension_response$_$this()._service_extension_response$_id = id; + }, + set$success(success) { + this.get$_service_extension_response$_$this()._service_extension_response$_success = success; + }, + set$errorMessage(errorMessage) { + this.get$_service_extension_response$_$this()._service_extension_response$_errorMessage = errorMessage; + }, + get$_service_extension_response$_$this() { + var _this = this, + $$v = _this._service_extension_response$_$v; + if ($$v != null) { + _this._service_extension_response$_id = $$v.id; + _this._resultJson = $$v.resultJson; + _this._service_extension_response$_success = $$v.success; + _this._errorCode = $$v.errorCode; + _this._service_extension_response$_errorMessage = $$v.errorMessage; + _this._service_extension_response$_$v = null; + } + return _this; + }, + _service_extension_response$_build$0() { + var t1, t2, t3, t4, t5, _this = this, + _s24_ = "ServiceExtensionResponse", + _$result = _this._service_extension_response$_$v; + if (_$result == null) { + t1 = type$.String; + t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_service_extension_response$_$this()._service_extension_response$_id, _s24_, "id", t1); + t3 = _this.get$_service_extension_response$_$this()._resultJson; + t4 = type$.bool; + t5 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_service_extension_response$_$this()._service_extension_response$_success, _s24_, "success", t4); + _$result = new A._$ServiceExtensionResponse(t2, t3, t5, _this.get$_service_extension_response$_$this()._errorCode, _this.get$_service_extension_response$_$this()._service_extension_response$_errorMessage); + A.BuiltValueNullFieldError_checkNotNull(t2, _s24_, "id", t1); + A.BuiltValueNullFieldError_checkNotNull(t5, _s24_, "success", t4); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.ServiceExtensionResponse); + return _this._service_extension_response$_$v = _$result; + } + }; + A.BatchedStreamController.prototype = { + _batchAndSendEvents$0() { + var $async$goto = 0, + $async$completer = A._makeAsyncAwaitCompleter(type$.void), + $async$self = this, t2, t3, t4, t5, t6, t7, t8, lastSendTime0, lastEvent, duration, t1, buffer, lastSendTime, $async$temp1, $async$temp2; + var $async$_batchAndSendEvents$0 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { + if ($async$errorCode === 1) + return A._asyncRethrow($async$result, $async$completer); + while (true) + switch ($async$goto) { + case 0: + // Function start + duration = A.Duration$(0, $async$self._checkDelayMilliseconds); + t1 = $async$self.$ti; + buffer = A._setArrayType([], t1._eval$1("JSArray<1>")); + lastSendTime = Date.now(); + t2 = $async$self._batchDelayMilliseconds, t3 = $async$self._outputController, t4 = A._instanceType(t3), t1 = t1._precomputed1, t5 = t4._precomputed1, t4 = t4._eval$1("_DelayedData<1>"); + case 2: + // for condition + $async$goto = 4; + return A._asyncAwait($async$self._hasEventOrTimeOut$1(duration), $async$_batchAndSendEvents$0); + case 4: + // returning from await. + if (!$async$result) { + // goto after for + $async$goto = 3; + break; + } + $async$goto = 7; + return A._asyncAwait($async$self._hasEventDuring$1(duration), $async$_batchAndSendEvents$0); + case 7: + // returning from await. + $async$goto = $async$result ? 5 : 6; + break; + case 5: + // then + t6 = $async$self.__BatchedStreamController__inputQueue_A; + t6 === $ && A.throwLateFieldNI("_inputQueue"); + t7 = t6.$ti; + t8 = new A._Future($.Zone__current, t7._eval$1("_Future<1>")); + t6._addRequest$1(new A._NextRequest(new A._AsyncCompleter(t8, t7._eval$1("_AsyncCompleter<1>")), t7._eval$1("_NextRequest<1>"))); + $async$temp1 = B.JSArray_methods; + $async$temp2 = buffer; + $async$goto = 8; + return A._asyncAwait(t8, $async$_batchAndSendEvents$0); + case 8: + // returning from await. + $async$temp1.add$1($async$temp2, $async$result); + case 6: + // join + lastSendTime0 = Date.now(); + if (lastSendTime0 > lastSendTime + t2) { + if (buffer.length !== 0) { + t6 = A.List_List$_of(buffer, t1); + t5._as(t6); + t7 = t3._state; + if (t7 >= 4) + A.throwExpression(t3._badEventState$0()); + if ((t7 & 1) !== 0) + t3._sendData$1(t6); + else if ((t7 & 3) === 0) { + t7 = t3._ensurePendingEvents$0(); + t6 = new A._DelayedData(t6, t4); + lastEvent = t7.lastPendingEvent; + if (lastEvent == null) + t7.firstPendingEvent = t7.lastPendingEvent = t6; + else { + lastEvent.set$next(t6); + t7.lastPendingEvent = t6; + } + } + B.JSArray_methods.clear$0(buffer); + } + lastSendTime = lastSendTime0; + } + // goto for condition + $async$goto = 2; + break; + case 3: + // after for + if (buffer.length !== 0) { + t1 = A.List_List$_of(buffer, t1); + t3.add$1(0, t5._as(t1)); + } + $async$self._completer.complete$1(true); + // implicit return + return A._asyncReturn(null, $async$completer); + } + }); return A._asyncStartSync($async$_batchAndSendEvents$0, $async$completer); }, _hasEventOrTimeOut$1(duration) { @@ -23625,13 +24051,13 @@ call$0() { return true; }, - $signature: 33 + $signature: 26 }; A.BatchedStreamController__hasEventDuring_closure.prototype = { call$0() { return false; }, - $signature: 33 + $signature: 26 }; A.SocketClient.prototype = {}; A.SseSocketClient.prototype = { @@ -23673,14 +24099,14 @@ call$1(o) { return J.toString$0$(o); }, - $signature: 43 + $signature: 44 }; A.safeUnawaited_closure.prototype = { call$2(error, stackTrace) { type$.StackTrace._as(stackTrace); return $.$get$_logger().log$4(B.Level_WARNING_900, "Error in unawaited Future:", error, stackTrace); }, - $signature: 21 + $signature: 25 }; A.Int32.prototype = { _toInt$1(val) { @@ -23852,13 +24278,13 @@ call$2(key1, key2) { return A._asString(key1).toLowerCase() === A._asString(key2).toLowerCase(); }, - $signature: 44 + $signature: 45 }; A.BaseRequest_closure0.prototype = { call$1(key) { return B.JSString_methods.get$hashCode(A._asString(key).toLowerCase()); }, - $signature: 45 + $signature: 46 }; A.BaseResponse.prototype = { BaseResponse$7$contentLength$headers$isRedirect$persistentConnection$reasonPhrase$request(statusCode, contentLength, headers, isRedirect, persistentConnection, reasonPhrase, request) { @@ -23996,7 +24422,7 @@ call$1(bytes) { return this.completer.complete$1(new Uint8Array(A._ensureNativeList(type$.List_int._as(bytes)))); }, - $signature: 47 + $signature: 48 }; A.ClientException.prototype = { toString$0(_) { @@ -24084,7 +24510,7 @@ scanner.expectDone$0(); return A.MediaType$(t4, t5, parameters); }, - $signature: 48 + $signature: 34 }; A.MediaType_toString_closure.prototype = { call$2(attribute, value) { @@ -24103,13 +24529,13 @@ } else t1._contents = t3 + value; }, - $signature: 49 + $signature: 50 }; A.MediaType_toString__closure.prototype = { call$1(match) { return "\\" + A.S(match.$index(0, 0)); }, - $signature: 31 + $signature: 28 }; A.expectQuotedString_closure.prototype = { call$1(match) { @@ -24117,7 +24543,7 @@ t1.toString; return t1; }, - $signature: 31 + $signature: 28 }; A.Level.prototype = { $eq(_, other) { @@ -24206,7 +24632,7 @@ $parent._children.$indexSet(0, thisName, t1); return t1; }, - $signature: 51 + $signature: 52 }; A.Context.prototype = { absolute$15(part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15) { @@ -24440,20 +24866,20 @@ call$1(part) { return A._asString(part) !== ""; }, - $signature: 30 + $signature: 20 }; A.Context_split_closure.prototype = { call$1(part) { return A._asString(part).length !== 0; }, - $signature: 30 + $signature: 20 }; A._validateArgList_closure.prototype = { call$1(arg) { A._asStringQ(arg); return arg == null ? "null" : '"' + arg + '"'; }, - $signature: 106 + $signature: 54 }; A.InternalStyle.prototype = { getRoot$1(path) { @@ -25348,7 +25774,7 @@ call$0() { return this.color; }, - $signature: 54 + $signature: 55 }; A.Highlighter$__closure.prototype = { call$1(line) { @@ -25356,34 +25782,34 @@ t2 = A._arrayInstanceType(t1); return new A.WhereIterable(t1, t2._eval$1("bool(1)")._as(new A.Highlighter$___closure()), t2._eval$1("WhereIterable<1>")).get$length(0); }, - $signature: 55 + $signature: 56 }; A.Highlighter$___closure.prototype = { call$1(highlight) { var t1 = type$._Highlight._as(highlight).span; return t1.get$start().get$line() !== t1.get$end().get$line(); }, - $signature: 15 + $signature: 19 }; A.Highlighter$__closure0.prototype = { call$1(line) { return type$._Line._as(line).url; }, - $signature: 57 + $signature: 58 }; A.Highlighter__collateLines_closure.prototype = { call$1(highlight) { var t1 = type$._Highlight._as(highlight).span.get$sourceUrl(); return t1 == null ? new A.Object() : t1; }, - $signature: 58 + $signature: 59 }; A.Highlighter__collateLines_closure0.prototype = { call$2(highlight1, highlight2) { var t1 = type$._Highlight; return t1._as(highlight1).span.compareTo$1(0, t1._as(highlight2).span); }, - $signature: 59 + $signature: 60 }; A.Highlighter__collateLines_closure1.prototype = { call$1(entry) { @@ -25426,20 +25852,20 @@ } return lines; }, - $signature: 60 + $signature: 61 }; A.Highlighter__collateLines__closure.prototype = { call$1(highlight) { return type$._Highlight._as(highlight).span.get$end().get$line() < this.line.number; }, - $signature: 15 + $signature: 19 }; A.Highlighter_highlight_closure.prototype = { call$1(highlight) { type$._Highlight._as(highlight); return true; }, - $signature: 15 + $signature: 19 }; A.Highlighter__writeFileStart_closure.prototype = { call$0() { @@ -25540,7 +25966,7 @@ t4 = B.JSString_methods.$mul("^", Math.max(endColumn + (tabsBefore + tabsInside) * 3 - startColumn, 1)); return (t2._contents += t4).length - t3.length; }, - $signature: 22 + $signature: 30 }; A.Highlighter__writeIndicator_closure0.prototype = { call$0() { @@ -25561,7 +25987,7 @@ t1._writeArrow$3$beginning(_this.line, Math.max(_this.highlight.span.get$end().get$column() - 1, 0), false); return t2._contents.length - t3.length; }, - $signature: 22 + $signature: 30 }; A.Highlighter__writeSidebar_closure.prototype = { call$0() { @@ -25597,7 +26023,7 @@ } return A._Highlight__normalizeEndOfLine(A._Highlight__normalizeTrailingNewline(A._Highlight__normalizeNewlines(newSpan))); }, - $signature: 62 + $signature: 63 }; A._Line.prototype = { toString$0(_) { @@ -25966,25 +26392,25 @@ }); return A._asyncStartSync($async$call$0, $async$completer); }, - $signature: 65 + $signature: 66 }; A.generateUuidV4_generateBits.prototype = { call$1(bitCount) { return this.random.nextInt$1(B.JSInt_methods._shlPositive$1(1, bitCount)); }, - $signature: 27 + $signature: 22 }; A.generateUuidV4_printDigits.prototype = { call$2(value, count) { return B.JSString_methods.padLeft$2(B.JSInt_methods.toRadixString$1(value, 16), count, "0"); }, - $signature: 20 + $signature: 32 }; A.generateUuidV4_bitsDigits.prototype = { call$2(bitCount, digitCount) { return this.printDigits.call$2(this.generateBits.call$1(bitCount), digitCount); }, - $signature: 20 + $signature: 32 }; A.GuaranteeChannel.prototype = { GuaranteeChannel$3$allowSinkErrors(innerSink, allowSinkErrors, _box_0, $T) { @@ -26538,7 +26964,7 @@ A._asString(webSocket._webSocket.protocol); t2._readyCompleter.complete$0(); }, - $signature: 67 + $signature: 68 }; A.AdapterWebSocketChannel__closure.prototype = { call$1($event) { @@ -26574,7 +27000,7 @@ } } }, - $signature: 68 + $signature: 69 }; A.AdapterWebSocketChannel__closure0.prototype = { call$1(obj) { @@ -26671,7 +27097,7 @@ }); return A._asyncStartSync($async$call$0, $async$completer); }, - $signature: 19 + $signature: 16 }; A.AdapterWebSocketChannel_closure0.prototype = { call$1(e) { @@ -26689,7 +27115,7 @@ t1 === $ && A.throwLateFieldNI("_sink"); t1.close$0(); }, - $signature: 69 + $signature: 70 }; A._WebSocketSink.prototype = {$isWebSocketSink: 1}; A.WebSocketChannelException.prototype = { @@ -26784,11 +27210,8 @@ A._sendConnectRequest(client.get$sink()); else A.runMain(); - else { + else A._sendConnectRequest(client.get$sink()); - if (A._asBool(t1.$useDwdsWebSocketConnection)) - A.runMain(); - } A._launchCommunicationWithDebugExtension(); // implicit return return A._asyncReturn(null, $async$completer); @@ -26796,7 +27219,7 @@ }); return A._asyncStartSync($async$call$0, $async$completer); }, - $signature: 19 + $signature: 16 }; A.main__closure.prototype = { call$0() { @@ -26826,7 +27249,7 @@ call$1(runId) { return this.call$2(runId, null); }, - $signature: 71 + $signature: 72 }; A.main__closure2.prototype = { call$0() { @@ -26853,7 +27276,7 @@ A._trySendEvent(t1, B.C_JsonCodec.encode$2$toEncodable(t2.serialize$1(t3._debug_event$_build$0()), null), type$.dynamic); } }, - $signature: 72 + $signature: 73 }; A.main___closure2.prototype = { call$1(b) { @@ -26862,7 +27285,7 @@ b.get$_debug_event$_$this().set$_events(t1); return t1; }, - $signature: 73 + $signature: 74 }; A.main__closure4.prototype = { call$2(kind, eventData) { @@ -26876,7 +27299,7 @@ A._trySendEvent(new A._StreamSinkWrapper(t1, A._instanceType(t1)._eval$1("_StreamSinkWrapper<1>")), t2._debug_event$_build$0(), type$.DebugEvent); } }, - $signature: 74 + $signature: 75 }; A.main___closure1.prototype = { call$1(b) { @@ -26886,7 +27309,7 @@ b.get$_debug_event$_$this()._eventData = this.eventData; return b; }, - $signature: 75 + $signature: 76 }; A.main__closure5.prototype = { call$1(eventData) { @@ -26898,7 +27321,7 @@ type$.nullable_void_Function_RegisterEventBuilder._as(new A.main___closure0(eventData)).call$1(t3); A._trySendEvent(t1, B.C_JsonCodec.encode$2$toEncodable(t2.serialize$1(t3._register_event$_build$0()), null), type$.dynamic); }, - $signature: 76 + $signature: 106 }; A.main___closure0.prototype = { call$1(b) { @@ -26907,7 +27330,7 @@ b.get$_register_event$_$this()._register_event$_eventData = this.eventData; return b; }, - $signature: 77 + $signature: 78 }; A.main__closure6.prototype = { call$0() { @@ -26933,7 +27356,7 @@ b.get$_devtools_request$_$this()._devtools_request$_instanceId = t1; return b; }, - $signature: 78 + $signature: 79 }; A.main__closure7.prototype = { call$1(serialized) { @@ -27043,14 +27466,29 @@ break; case 24: // else - $async$goto = $event instanceof A._$HotReloadRequest ? 25 : 26; + $async$goto = $event instanceof A._$HotReloadRequest ? 25 : 27; break; case 25: // then - $async$goto = 27; + $async$goto = 28; return A._asyncAwait(A.handleWebSocketHotReloadRequest($event, $async$self.manager, $async$self.client.get$sink()), $async$call$1); + case 28: + // returning from await. + // goto join + $async$goto = 26; + break; case 27: + // else + $async$goto = $event instanceof A._$ServiceExtensionRequest ? 29 : 30; + break; + case 29: + // then + $async$goto = 31; + return A._asyncAwait(A.handleServiceExtensionRequest($event, $async$self.client.get$sink(), $async$self.manager), $async$call$1); + case 31: // returning from await. + case 30: + // join case 26: // join case 23: @@ -27067,7 +27505,7 @@ }); return A._asyncStartSync($async$call$1, $async$completer); }, - $signature: 79 + $signature: 80 }; A.main__closure8.prototype = { call$1(error) { @@ -27091,7 +27529,7 @@ type$.StackTrace._as(stackTrace); A.print("Unhandled error detected in the injected client.js script.\n\nYou can disable this script in webdev by passing --no-injected-client if it\nis preventing your app from loading, but note that this will also prevent\nall debugging and hot reload/restart functionality from working.\n\nThe original error is below, please file an issue at\nhttps://github.com/dart-lang/webdev/issues/new and attach this output:\n\n" + A.S(error) + "\n" + stackTrace.toString$0(0) + "\n"); }, - $signature: 14 + $signature: 13 }; A._sendConnectRequest_closure.prototype = { call$1(b) { @@ -27104,7 +27542,7 @@ b.get$_connect_request$_$this()._entrypointPath = t1; return b; }, - $signature: 80 + $signature: 81 }; A._launchCommunicationWithDebugExtension_closure.prototype = { call$1(b) { @@ -27134,13 +27572,13 @@ b.get$_$this()._workspaceName = t1; return b; }, - $signature: 81 + $signature: 82 }; A._handleAuthRequest_closure.prototype = { call$1(isAuthenticated) { return A._dispatchEvent("dart-auth-response", "" + A._asBool(isAuthenticated)); }, - $signature: 82 + $signature: 83 }; A._sendResponse_closure.prototype = { call$1(b) { @@ -27297,6 +27735,78 @@ }); return A._asyncStartSync($async$fetchLibrariesForHotReload$1, $async$completer); }, + handleServiceExtension$2(method, args) { + return this.handleServiceExtension$body$DdcLibraryBundleRestarter(method, type$.Map_String_dynamic._as(args)); + }, + handleServiceExtension$body$DdcLibraryBundleRestarter(method, args) { + var $async$goto = 0, + $async$completer = A._makeAsyncAwaitCompleter(type$.nullable_Map_String_dynamic), + $async$returnValue, t1, t2, params, $async$temp1, $async$temp2; + var $async$handleServiceExtension$2 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { + if ($async$errorCode === 1) + return A._asyncRethrow($async$result, $async$completer); + while (true) + switch ($async$goto) { + case 0: + // Function start + $async$goto = method === "ext.flutter.reassemble" ? 3 : 5; + break; + case 3: + // then + t1 = type$.JSObject; + $async$goto = 6; + return A._asyncAwait(A._Debugger_maybeInvokeFlutterReassemble(t1._as(t1._as(init.G.dartDevEmbedder).debugger)), $async$handleServiceExtension$2); + case 6: + // returning from await. + $async$returnValue = A.LinkedHashMap_LinkedHashMap$_literal(["status", "reassemble invoked"], type$.String, type$.dynamic); + // goto return + $async$goto = 1; + break; + // goto join + $async$goto = 4; + break; + case 5: + // else + $async$goto = method === "getExtensionRpcs" ? 7 : 9; + break; + case 7: + // then + t1 = type$.JSObject; + t1 = type$.JSArray_nullable_Object._as(t1._as(t1._as(init.G.dartDevEmbedder).debugger).extensionNames); + t1 = type$.List_String._is(t1) ? t1 : new A.CastList(t1, A._arrayInstanceType(t1)._eval$1("CastList<1,String>")); + t2 = type$.String; + $async$returnValue = A.LinkedHashMap_LinkedHashMap$_literal(["rpcs", J.cast$1$0$ax(t1, t2)], t2, type$.dynamic); + // goto return + $async$goto = 1; + break; + // goto join + $async$goto = 8; + break; + case 9: + // else + params = args.get$isNotEmpty(args) ? B.C_JsonCodec.encode$2$toEncodable(args, null) : "{}"; + t1 = type$.JSObject; + $async$temp1 = type$.Map_String_dynamic; + $async$temp2 = B.C_JsonCodec; + $async$goto = 10; + return A._asyncAwait(A.promiseToFuture(t1._as(t1._as(t1._as(init.G.dartDevEmbedder).debugger).invokeExtension(method, params)), type$.String), $async$handleServiceExtension$2); + case 10: + // returning from await. + $async$returnValue = $async$temp1._as($async$temp2.decode$2$reviver($async$result, null)); + // goto return + $async$goto = 1; + break; + case 8: + // join + case 4: + // join + case 1: + // return + return A._asyncReturn($async$returnValue, $async$completer); + } + }); + return A._asyncStartSync($async$handleServiceExtension$2, $async$completer); + }, $isRestarter: 1 }; A.DdcLibraryBundleRestarter_restart_closure.prototype = { @@ -27307,7 +27817,7 @@ t1._as(t1._as(init.G.dartDevEmbedder).config).capturedMainHandler = null; A.safeUnawaited(this.$this._runMainWhenReady$2(this.readyToRunMain, runMain)); }, - $signature: 83 + $signature: 84 }; A.DdcLibraryBundleRestarter_fetchLibrariesForHotReload_closure.prototype = { call$0() { @@ -27369,7 +27879,7 @@ this.sub.cancel$0(); return value; }, - $signature: 84 + $signature: 85 }; A.ReloadingManager.prototype = { hotRestart$2$readyToRunMain$runId(readyToRunMain, runId) { @@ -27430,6 +27940,38 @@ }); return A._asyncStartSync($async$hotReload$0, $async$completer); }, + handleServiceExtension$2(method, args) { + return this.handleServiceExtension$body$ReloadingManager(method, type$.Map_String_dynamic._as(args)); + }, + handleServiceExtension$body$ReloadingManager(method, args) { + var $async$goto = 0, + $async$completer = A._makeAsyncAwaitCompleter(type$.nullable_Map_String_dynamic), + $async$returnValue, $async$self = this, restarter; + var $async$handleServiceExtension$2 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { + if ($async$errorCode === 1) + return A._asyncRethrow($async$result, $async$completer); + while (true) + switch ($async$goto) { + case 0: + // Function start + restarter = $async$self._restarter; + if (restarter instanceof A.DdcLibraryBundleRestarter) { + $async$returnValue = restarter.handleServiceExtension$2(method, args); + // goto return + $async$goto = 1; + break; + } + $async$returnValue = null; + // goto return + $async$goto = 1; + break; + case 1: + // return + return A._asyncReturn($async$returnValue, $async$completer); + } + }); + return A._asyncStartSync($async$handleServiceExtension$2, $async$completer); + }, _afterRestart$1(succeeded) { var t1, t2; if (!succeeded) @@ -27836,7 +28378,7 @@ call$1(e) { this.completer.completeError$2(new A.HotReloadFailedException(A._asString(type$.JavaScriptObject._as(e).message)), this.stackTrace); }, - $signature: 87 + $signature: 88 }; A._createScript_closure.prototype = { call$0() { @@ -27845,7 +28387,7 @@ return new A._createScript__closure(); return new A._createScript__closure0(nonce); }, - $signature: 88 + $signature: 89 }; A._createScript__closure.prototype = { call$0() { @@ -27910,51 +28452,51 @@ _instance_1_i = hunkHelpers._instance_1i, _instance_0_u = hunkHelpers._instance_0u, _instance_1_u = hunkHelpers._instance_1u; - _static_2(J, "_interceptors_JSArray__compareAny$closure", "JSArray__compareAny", 28); - _static_1(A, "async__AsyncRun__scheduleImmediateJsOverride$closure", "_AsyncRun__scheduleImmediateJsOverride", 16); - _static_1(A, "async__AsyncRun__scheduleImmediateWithSetImmediate$closure", "_AsyncRun__scheduleImmediateWithSetImmediate", 16); - _static_1(A, "async__AsyncRun__scheduleImmediateWithTimer$closure", "_AsyncRun__scheduleImmediateWithTimer", 16); + _static_2(J, "_interceptors_JSArray__compareAny$closure", "JSArray__compareAny", 33); + _static_1(A, "async__AsyncRun__scheduleImmediateJsOverride$closure", "_AsyncRun__scheduleImmediateJsOverride", 12); + _static_1(A, "async__AsyncRun__scheduleImmediateWithSetImmediate$closure", "_AsyncRun__scheduleImmediateWithSetImmediate", 12); + _static_1(A, "async__AsyncRun__scheduleImmediateWithTimer$closure", "_AsyncRun__scheduleImmediateWithTimer", 12); _static_0(A, "async___startMicrotaskLoop$closure", "_startMicrotaskLoop", 0); _static_1(A, "async___nullDataHandler$closure", "_nullDataHandler", 7); - _static_2(A, "async___nullErrorHandler$closure", "_nullErrorHandler", 14); + _static_2(A, "async___nullErrorHandler$closure", "_nullErrorHandler", 13); _static_0(A, "async___nullDoneHandler$closure", "_nullDoneHandler", 0); - _static(A, "async___rootHandleUncaughtError$closure", 5, null, ["call$5"], ["_rootHandleUncaughtError"], 91, 0); + _static(A, "async___rootHandleUncaughtError$closure", 5, null, ["call$5"], ["_rootHandleUncaughtError"], 92, 0); _static(A, "async___rootRun$closure", 4, null, ["call$1$4", "call$4"], ["_rootRun", function($self, $parent, zone, f) { f.toString; return A._rootRun($self, $parent, zone, f, type$.dynamic); - }], 92, 0); + }], 93, 0); _static(A, "async___rootRunUnary$closure", 5, null, ["call$2$5", "call$5"], ["_rootRunUnary", function($self, $parent, zone, f, arg) { var t1 = type$.dynamic; f.toString; return A._rootRunUnary($self, $parent, zone, f, arg, t1, t1); - }], 93, 0); - _static(A, "async___rootRunBinary$closure", 6, null, ["call$3$6"], ["_rootRunBinary"], 94, 0); + }], 94, 0); + _static(A, "async___rootRunBinary$closure", 6, null, ["call$3$6"], ["_rootRunBinary"], 95, 0); _static(A, "async___rootRegisterCallback$closure", 4, null, ["call$1$4", "call$4"], ["_rootRegisterCallback", function($self, $parent, zone, f) { f.toString; return A._rootRegisterCallback($self, $parent, zone, f, type$.dynamic); - }], 95, 0); + }], 96, 0); _static(A, "async___rootRegisterUnaryCallback$closure", 4, null, ["call$2$4", "call$4"], ["_rootRegisterUnaryCallback", function($self, $parent, zone, f) { var t1 = type$.dynamic; f.toString; return A._rootRegisterUnaryCallback($self, $parent, zone, f, t1, t1); - }], 96, 0); + }], 97, 0); _static(A, "async___rootRegisterBinaryCallback$closure", 4, null, ["call$3$4", "call$4"], ["_rootRegisterBinaryCallback", function($self, $parent, zone, f) { var t1 = type$.dynamic; f.toString; return A._rootRegisterBinaryCallback($self, $parent, zone, f, t1, t1, t1); - }], 97, 0); - _static(A, "async___rootErrorCallback$closure", 5, null, ["call$5"], ["_rootErrorCallback"], 98, 0); - _static(A, "async___rootScheduleMicrotask$closure", 4, null, ["call$4"], ["_rootScheduleMicrotask"], 99, 0); - _static(A, "async___rootCreateTimer$closure", 5, null, ["call$5"], ["_rootCreateTimer"], 100, 0); - _static(A, "async___rootCreatePeriodicTimer$closure", 5, null, ["call$5"], ["_rootCreatePeriodicTimer"], 101, 0); - _static(A, "async___rootPrint$closure", 4, null, ["call$4"], ["_rootPrint"], 102, 0); - _static_1(A, "async___printToZone$closure", "_printToZone", 103); - _static(A, "async___rootFork$closure", 5, null, ["call$5"], ["_rootFork"], 104, 0); - _instance(A._Completer.prototype, "get$completeError", 0, 1, null, ["call$2", "call$1"], ["completeError$2", "completeError$1"], 32, 0, 0); - _instance_2_u(A._Future.prototype, "get$_completeError", "_completeError$2", 14); + }], 98, 0); + _static(A, "async___rootErrorCallback$closure", 5, null, ["call$5"], ["_rootErrorCallback"], 99, 0); + _static(A, "async___rootScheduleMicrotask$closure", 4, null, ["call$4"], ["_rootScheduleMicrotask"], 100, 0); + _static(A, "async___rootCreateTimer$closure", 5, null, ["call$5"], ["_rootCreateTimer"], 101, 0); + _static(A, "async___rootCreatePeriodicTimer$closure", 5, null, ["call$5"], ["_rootCreatePeriodicTimer"], 102, 0); + _static(A, "async___rootPrint$closure", 4, null, ["call$4"], ["_rootPrint"], 103, 0); + _static_1(A, "async___printToZone$closure", "_printToZone", 104); + _static(A, "async___rootFork$closure", 5, null, ["call$5"], ["_rootFork"], 105, 0); + _instance(A._Completer.prototype, "get$completeError", 0, 1, null, ["call$2", "call$1"], ["completeError$2", "completeError$1"], 23, 0, 0); + _instance_2_u(A._Future.prototype, "get$_completeError", "_completeError$2", 13); var _; _instance_1_i(_ = A._StreamController.prototype, "get$add", "add$1", 9); - _instance(_, "get$addError", 0, 1, null, ["call$2", "call$1"], ["addError$2", "addError$1"], 32, 0, 0); + _instance(_, "get$addError", 0, 1, null, ["call$2", "call$1"], ["addError$2", "addError$1"], 23, 0, 0); _instance_0_u(_ = A._ControllerSubscription.prototype, "get$_onPause", "_onPause$0", 0); _instance_0_u(_, "get$_onResume", "_onResume$0", 0); _instance_0_u(_ = A._BufferingStreamSubscription.prototype, "get$_onPause", "_onPause$0", 0); @@ -27963,43 +28505,43 @@ _instance_0_u(_ = A._ForwardingStreamSubscription.prototype, "get$_onPause", "_onPause$0", 0); _instance_0_u(_, "get$_onResume", "_onResume$0", 0); _instance_1_u(_, "get$_handleData", "_handleData$1", 9); - _instance_2_u(_, "get$_handleError", "_handleError$2", 21); + _instance_2_u(_, "get$_handleError", "_handleError$2", 25); _instance_0_u(_, "get$_handleDone", "_handleDone$0", 0); - _static_2(A, "collection___defaultEquals$closure", "_defaultEquals0", 18); - _static_1(A, "collection___defaultHashCode$closure", "_defaultHashCode", 17); - _static_2(A, "collection_ListBase__compareAny$closure", "ListBase__compareAny", 28); + _static_2(A, "collection___defaultEquals$closure", "_defaultEquals0", 17); + _static_1(A, "collection___defaultHashCode$closure", "_defaultHashCode", 18); + _static_2(A, "collection_ListBase__compareAny$closure", "ListBase__compareAny", 33); _static_1(A, "convert___defaultToEncodable$closure", "_defaultToEncodable", 4); _instance_1_i(_ = A._ByteCallbackSink.prototype, "get$add", "add$1", 9); _instance_0_u(_, "get$close", "close$0", 0); - _static_1(A, "core__identityHashCode$closure", "identityHashCode", 17); - _static_2(A, "core__identical$closure", "identical", 18); - _static_1(A, "core_Uri_decodeComponent$closure", "Uri_decodeComponent", 13); + _static_1(A, "core__identityHashCode$closure", "identityHashCode", 18); + _static_2(A, "core__identical$closure", "identical", 17); + _static_1(A, "core_Uri_decodeComponent$closure", "Uri_decodeComponent", 15); _static(A, "math__max$closure", 2, null, ["call$1$2", "call$2"], ["max", function(a, b) { a.toString; b.toString; return A.max(a, b, type$.num); - }], 105, 0); - _instance_2_u(_ = A.DeepCollectionEquality.prototype, "get$equals", "equals$2", 18); - _instance_1_u(_, "get$hash", "hash$1", 17); - _instance_1_u(_, "get$isValidKey", "isValidKey$1", 12); + }], 77, 0); + _instance_2_u(_ = A.DeepCollectionEquality.prototype, "get$equals", "equals$2", 17); + _instance_1_u(_, "get$hash", "hash$1", 18); + _instance_1_u(_, "get$isValidKey", "isValidKey$1", 14); _static(A, "hot_reload_response_HotReloadResponse___new_tearOff$closure", 0, null, ["call$1", "call$0"], ["HotReloadResponse___new_tearOff", function() { return A.HotReloadResponse___new_tearOff(null); - }], 70, 0); - _static_1(A, "case_insensitive_map_CaseInsensitiveMap__canonicalizer$closure", "CaseInsensitiveMap__canonicalizer", 13); + }], 71, 0); + _static_1(A, "case_insensitive_map_CaseInsensitiveMap__canonicalizer$closure", "CaseInsensitiveMap__canonicalizer", 15); _instance_1_u(_ = A.SseClient.prototype, "get$_onIncomingControlMessage", "_onIncomingControlMessage$1", 2); _instance_1_u(_, "get$_onIncomingMessage", "_onIncomingMessage$1", 2); _instance_0_u(_, "get$_onOutgoingDone", "_onOutgoingDone$0", 0); - _instance_1_u(_, "get$_onOutgoingMessage", "_onOutgoingMessage$1", 64); + _instance_1_u(_, "get$_onOutgoingMessage", "_onOutgoingMessage$1", 65); _static_1(A, "client___handleAuthRequest$closure", "_handleAuthRequest", 2); - _instance_1_u(_ = A.RequireRestarter.prototype, "get$_moduleParents", "_moduleParents$1", 85); - _instance_2_u(_, "get$_moduleTopologicalCompare", "_moduleTopologicalCompare$2", 86); + _instance_1_u(_ = A.RequireRestarter.prototype, "get$_moduleParents", "_moduleParents$1", 86); + _instance_2_u(_, "get$_moduleTopologicalCompare", "_moduleTopologicalCompare$2", 87); })(); (function inheritance() { var _mixin = hunkHelpers.mixin, _inherit = hunkHelpers.inherit, _inheritMany = hunkHelpers.inheritMany; _inherit(A.Object, null); - _inheritMany(A.Object, [A.JS_CONST, J.Interceptor, J.ArrayIterator, A.Iterable, A.CastIterator, A.Closure, A.MapBase, A.Error, A.ListBase, A.SentinelValue, A.ListIterator, A.MappedIterator, A.WhereIterator, A.ExpandIterator, A.TakeIterator, A.SkipIterator, A.EmptyIterator, A.WhereTypeIterator, A.FixedLengthListMixin, A.UnmodifiableListMixin, A._Record, A.ConstantMap, A._KeysOrValuesOrElementsIterator, A.TypeErrorDecoder, A.NullThrownFromJavaScriptException, A.ExceptionAndStackTrace, A._StackTrace, A.LinkedHashMapCell, A.LinkedHashMapKeyIterator, A.LinkedHashMapValueIterator, A.LinkedHashMapEntryIterator, A.JSSyntaxRegExp, A._MatchImplementation, A._AllMatchesIterator, A.StringMatch, A._StringAllMatchesIterator, A._Cell, A._UnmodifiableNativeByteBufferView, A.Rti, A._FunctionParameters, A._Type, A._TimerImpl, A._AsyncAwaitCompleter, A.AsyncError, A.TimeoutException, A._Completer, A._FutureListener, A._Future, A._AsyncCallbackEntry, A.Stream, A._StreamController, A._SyncStreamControllerDispatch, A._AsyncStreamControllerDispatch, A._BufferingStreamSubscription, A._StreamSinkWrapper, A._DelayedEvent, A._DelayedDone, A._PendingEvents, A._DoneStreamSubscription, A._StreamIterator, A._ZoneFunction, A._ZoneSpecification, A._ZoneDelegate, A._Zone, A._HashMapKeyIterator, A.SetBase, A._HashSetIterator, A._LinkedHashSetCell, A._LinkedHashSetIterator, A._UnmodifiableMapMixin, A.MapView, A._ListQueueIterator, A._SplayTreeNode, A._SplayTree, A._SplayTreeIterator, A.Codec, A.Converter, A._Base64Encoder, A._Base64Decoder, A.ByteConversionSink, A._JsonStringifier, A._Utf8Encoder, A._Utf8Decoder, A._BigIntImpl, A.DateTime, A.Duration, A.OutOfMemoryError, A.StackOverflowError, A._Exception, A.FormatException, A.IntegerDivisionByZeroException, A.MapEntry, A.Null, A._StringStackTrace, A.StringBuffer, A._Uri, A.UriData, A._SimpleUri, A.NullRejectionException, A._JSRandom, A._JSSecureRandom, A.AsyncMemoizer, A.DelegatingStreamSink, A.ErrorResult, A.ValueResult, A.StreamQueue, A._NextRequest, A._HasNextRequest, A.BuiltList, A.ListBuilder, A.BuiltListMultimap, A.ListMultimapBuilder, A.BuiltMap, A.MapBuilder, A.BuiltSet, A.SetBuilder, A.BuiltSetMultimap, A.SetMultimapBuilder, A.EnumClass, A.IndentingBuiltValueToStringHelper, A.JsonObject, A.FullType, A.BigIntSerializer, A.BoolSerializer, A.BuiltJsonSerializers, A.BuiltJsonSerializersBuilder, A.BuiltListMultimapSerializer, A.BuiltListSerializer, A.BuiltMapSerializer, A.BuiltSetMultimapSerializer, A.BuiltSetSerializer, A.DateTimeSerializer, A.DoubleSerializer, A.DurationSerializer, A.Int32Serializer, A.Int64Serializer, A.IntSerializer, A.JsonObjectSerializer, A.NullSerializer, A.NumSerializer, A.RegExpSerializer, A.StringSerializer, A.Uint8ListSerializer, A.UriSerializer, A.CanonicalizedMap, A.DefaultEquality, A.IterableEquality, A.ListEquality, A._UnorderedEquality, A._MapEntry, A.MapEquality, A.DeepCollectionEquality, A._QueueList_Object_ListMixin, A.BuildResult, A._$BuildStatusSerializer, A._$BuildResultSerializer, A.BuildResultBuilder, A.ConnectRequest, A._$ConnectRequestSerializer, A.ConnectRequestBuilder, A.DebugEvent, A.BatchedDebugEvents, A._$DebugEventSerializer, A._$BatchedDebugEventsSerializer, A.DebugEventBuilder, A.BatchedDebugEventsBuilder, A.DebugInfo, A._$DebugInfoSerializer, A.DebugInfoBuilder, A.DevToolsRequest, A.DevToolsResponse, A._$DevToolsRequestSerializer, A._$DevToolsResponseSerializer, A.DevToolsRequestBuilder, A.DevToolsResponseBuilder, A.ErrorResponse, A._$ErrorResponseSerializer, A.ErrorResponseBuilder, A.ExtensionRequest, A.ExtensionResponse, A.ExtensionEvent, A.BatchedEvents, A._$ExtensionRequestSerializer, A._$ExtensionResponseSerializer, A._$ExtensionEventSerializer, A._$BatchedEventsSerializer, A.ExtensionRequestBuilder, A.ExtensionResponseBuilder, A.ExtensionEventBuilder, A.BatchedEventsBuilder, A.HotReloadRequest, A._$HotReloadRequestSerializer, A.HotReloadRequestBuilder, A.HotReloadResponse, A._$HotReloadResponseSerializer, A.HotReloadResponseBuilder, A.IsolateExit, A.IsolateStart, A._$IsolateExitSerializer, A._$IsolateStartSerializer, A.IsolateExitBuilder, A.IsolateStartBuilder, A.RegisterEvent, A._$RegisterEventSerializer, A.RegisterEventBuilder, A.RunRequest, A._$RunRequestSerializer, A.BatchedStreamController, A.SocketClient, A.Int32, A.Int64, A._StackState, A.BaseClient, A.BaseRequest, A.BaseResponse, A.ClientException, A.MediaType, A.Level, A.LogRecord, A.Logger, A.Context, A.Style, A.ParsedPath, A.PathException, A.Pool, A.PoolResource, A.SourceFile, A.SourceLocationMixin, A.SourceSpanMixin, A.Highlighter, A._Highlight, A._Line, A.SourceLocation, A.SourceSpanException, A.StreamChannelMixin, A._GuaranteeSink, A.StreamChannelController, A.StringScanner, A.RNG, A.UuidV1, A.EventStreamProvider, A._EventStreamSubscription, A.BrowserWebSocket, A.WebSocketEvent, A.WebSocketException, A.WebSocketChannelException, A.DdcLibraryBundleRestarter, A.DdcRestarter, A.ReloadingManager, A.HotReloadFailedException, A.RequireRestarter]); + _inheritMany(A.Object, [A.JS_CONST, J.Interceptor, J.ArrayIterator, A.Iterable, A.CastIterator, A.Closure, A.MapBase, A.Error, A.ListBase, A.SentinelValue, A.ListIterator, A.MappedIterator, A.WhereIterator, A.ExpandIterator, A.TakeIterator, A.SkipIterator, A.EmptyIterator, A.WhereTypeIterator, A.FixedLengthListMixin, A.UnmodifiableListMixin, A._Record, A.ConstantMap, A._KeysOrValuesOrElementsIterator, A.TypeErrorDecoder, A.NullThrownFromJavaScriptException, A.ExceptionAndStackTrace, A._StackTrace, A.LinkedHashMapCell, A.LinkedHashMapKeyIterator, A.LinkedHashMapValueIterator, A.LinkedHashMapEntryIterator, A.JSSyntaxRegExp, A._MatchImplementation, A._AllMatchesIterator, A.StringMatch, A._StringAllMatchesIterator, A._Cell, A._UnmodifiableNativeByteBufferView, A.Rti, A._FunctionParameters, A._Type, A._TimerImpl, A._AsyncAwaitCompleter, A.AsyncError, A.TimeoutException, A._Completer, A._FutureListener, A._Future, A._AsyncCallbackEntry, A.Stream, A._StreamController, A._SyncStreamControllerDispatch, A._AsyncStreamControllerDispatch, A._BufferingStreamSubscription, A._StreamSinkWrapper, A._DelayedEvent, A._DelayedDone, A._PendingEvents, A._DoneStreamSubscription, A._StreamIterator, A._ZoneFunction, A._ZoneSpecification, A._ZoneDelegate, A._Zone, A._HashMapKeyIterator, A.SetBase, A._HashSetIterator, A._LinkedHashSetCell, A._LinkedHashSetIterator, A._UnmodifiableMapMixin, A.MapView, A._ListQueueIterator, A._SplayTreeNode, A._SplayTree, A._SplayTreeIterator, A.Codec, A.Converter, A._Base64Encoder, A._Base64Decoder, A.ByteConversionSink, A._JsonStringifier, A._Utf8Encoder, A._Utf8Decoder, A._BigIntImpl, A.DateTime, A.Duration, A.OutOfMemoryError, A.StackOverflowError, A._Exception, A.FormatException, A.IntegerDivisionByZeroException, A.MapEntry, A.Null, A._StringStackTrace, A.StringBuffer, A._Uri, A.UriData, A._SimpleUri, A.NullRejectionException, A._JSRandom, A._JSSecureRandom, A.AsyncMemoizer, A.DelegatingStreamSink, A.ErrorResult, A.ValueResult, A.StreamQueue, A._NextRequest, A._HasNextRequest, A.BuiltList, A.ListBuilder, A.BuiltListMultimap, A.ListMultimapBuilder, A.BuiltMap, A.MapBuilder, A.BuiltSet, A.SetBuilder, A.BuiltSetMultimap, A.SetMultimapBuilder, A.EnumClass, A.IndentingBuiltValueToStringHelper, A.JsonObject, A.FullType, A.BigIntSerializer, A.BoolSerializer, A.BuiltJsonSerializers, A.BuiltJsonSerializersBuilder, A.BuiltListMultimapSerializer, A.BuiltListSerializer, A.BuiltMapSerializer, A.BuiltSetMultimapSerializer, A.BuiltSetSerializer, A.DateTimeSerializer, A.DoubleSerializer, A.DurationSerializer, A.Int32Serializer, A.Int64Serializer, A.IntSerializer, A.JsonObjectSerializer, A.NullSerializer, A.NumSerializer, A.RegExpSerializer, A.StringSerializer, A.Uint8ListSerializer, A.UriSerializer, A.CanonicalizedMap, A.DefaultEquality, A.IterableEquality, A.ListEquality, A._UnorderedEquality, A._MapEntry, A.MapEquality, A.DeepCollectionEquality, A._QueueList_Object_ListMixin, A.BuildResult, A._$BuildStatusSerializer, A._$BuildResultSerializer, A.BuildResultBuilder, A.ConnectRequest, A._$ConnectRequestSerializer, A.ConnectRequestBuilder, A.DebugEvent, A.BatchedDebugEvents, A._$DebugEventSerializer, A._$BatchedDebugEventsSerializer, A.DebugEventBuilder, A.BatchedDebugEventsBuilder, A.DebugInfo, A._$DebugInfoSerializer, A.DebugInfoBuilder, A.DevToolsRequest, A.DevToolsResponse, A._$DevToolsRequestSerializer, A._$DevToolsResponseSerializer, A.DevToolsRequestBuilder, A.DevToolsResponseBuilder, A.ErrorResponse, A._$ErrorResponseSerializer, A.ErrorResponseBuilder, A.ExtensionRequest, A.ExtensionResponse, A.ExtensionEvent, A.BatchedEvents, A._$ExtensionRequestSerializer, A._$ExtensionResponseSerializer, A._$ExtensionEventSerializer, A._$BatchedEventsSerializer, A.ExtensionRequestBuilder, A.ExtensionResponseBuilder, A.ExtensionEventBuilder, A.BatchedEventsBuilder, A.HotReloadRequest, A._$HotReloadRequestSerializer, A.HotReloadRequestBuilder, A.HotReloadResponse, A._$HotReloadResponseSerializer, A.HotReloadResponseBuilder, A.IsolateExit, A.IsolateStart, A._$IsolateExitSerializer, A._$IsolateStartSerializer, A.IsolateExitBuilder, A.IsolateStartBuilder, A.RegisterEvent, A._$RegisterEventSerializer, A.RegisterEventBuilder, A.RunRequest, A._$RunRequestSerializer, A.ServiceExtensionRequest, A._$ServiceExtensionRequestSerializer, A.ServiceExtensionRequestBuilder, A.ServiceExtensionResponse, A._$ServiceExtensionResponseSerializer, A.ServiceExtensionResponseBuilder, A.BatchedStreamController, A.SocketClient, A.Int32, A.Int64, A._StackState, A.BaseClient, A.BaseRequest, A.BaseResponse, A.ClientException, A.MediaType, A.Level, A.LogRecord, A.Logger, A.Context, A.Style, A.ParsedPath, A.PathException, A.Pool, A.PoolResource, A.SourceFile, A.SourceLocationMixin, A.SourceSpanMixin, A.Highlighter, A._Highlight, A._Line, A.SourceLocation, A.SourceSpanException, A.StreamChannelMixin, A._GuaranteeSink, A.StreamChannelController, A.StringScanner, A.RNG, A.UuidV1, A.EventStreamProvider, A._EventStreamSubscription, A.BrowserWebSocket, A.WebSocketEvent, A.WebSocketException, A.WebSocketChannelException, A.DdcLibraryBundleRestarter, A.DdcRestarter, A.ReloadingManager, A.HotReloadFailedException, A.RequireRestarter]); _inheritMany(J.Interceptor, [J.JSBool, J.JSNull, J.JavaScriptObject, J.JavaScriptBigInt, J.JavaScriptSymbol, J.JSNumber, J.JSString]); _inheritMany(J.JavaScriptObject, [J.LegacyJavaScriptObject, J.JSArray, A.NativeByteBuffer, A.NativeTypedData]); _inheritMany(J.LegacyJavaScriptObject, [J.PlainJavaScriptObject, J.UnknownJavaScriptObject, J.JavaScriptFunction]); @@ -28009,7 +28551,7 @@ _inheritMany(A._CastIterableBase, [A.CastIterable, A.__CastListBase__CastIterableBase_ListMixin]); _inherit(A._EfficientLengthCastIterable, A.CastIterable); _inherit(A._CastListBase, A.__CastListBase__CastIterableBase_ListMixin); - _inheritMany(A.Closure, [A.Closure2Args, A.Closure0Args, A.Instantiation, A.TearOffClosure, A.initHooks_closure, A.initHooks_closure1, A._AsyncRun__initializeScheduleImmediate_internalCallback, A._AsyncRun__initializeScheduleImmediate_closure, A._awaitOnObject_closure, A._Future__propagateToListeners_handleWhenCompleteCallback_closure, A._Future_timeout_closure0, A.Stream_length_closure, A.Stream_first_closure0, A._CustomZone_bindUnaryCallback_closure, A._CustomZone_bindUnaryCallbackGuarded_closure, A._RootZone_bindUnaryCallback_closure, A._RootZone_bindUnaryCallbackGuarded_closure, A.runZonedGuarded_closure, A._CustomHashMap_closure, A._LinkedCustomHashMap_closure, A._BigIntImpl_hashCode_finish, A._Uri__makePath_closure, A.FutureOfJSAnyToJSPromise_get_toJS__closure, A.FutureOfVoidToJSPromise_get_toJS__closure, A.jsify__convert, A.promiseToFuture_closure, A.promiseToFuture_closure0, A.dartify_convert, A.StreamQueue__ensureListening_closure, A.BuiltListMultimap_BuiltListMultimap_closure, A.BuiltListMultimap_hashCode_closure, A.ListMultimapBuilder_replace_closure, A.BuiltMap_BuiltMap_closure, A.BuiltMap_hashCode_closure, A.BuiltSet_hashCode_closure, A.BuiltSetMultimap_hashCode_closure, A.SetMultimapBuilder_replace_closure, A.newBuiltValueToStringHelper_closure, A.BuiltListMultimapSerializer_serialize_closure, A.BuiltListMultimapSerializer_deserialize_closure, A.BuiltListSerializer_serialize_closure, A.BuiltListSerializer_deserialize_closure, A.BuiltSetMultimapSerializer_serialize_closure, A.BuiltSetMultimapSerializer_deserialize_closure, A.BuiltSetSerializer_serialize_closure, A.BuiltSetSerializer_deserialize_closure, A.CanonicalizedMap_keys_closure, A.WebSocketClient_stream_closure, A.BaseRequest_closure0, A.BrowserClient_send_closure, A.BrowserClient_send_closure0, A.ByteStream_toBytes_closure, A.MediaType_toString__closure, A.expectQuotedString_closure, A.Context_joinAll_closure, A.Context_split_closure, A._validateArgList_closure, A.Pool__runOnRelease_closure, A.Highlighter$__closure, A.Highlighter$___closure, A.Highlighter$__closure0, A.Highlighter__collateLines_closure, A.Highlighter__collateLines_closure1, A.Highlighter__collateLines__closure, A.Highlighter_highlight_closure, A.SseClient_closure0, A.SseClient_closure1, A.generateUuidV4_generateBits, A._GuaranteeSink__addError_closure, A._EventStreamSubscription_closure, A._EventStreamSubscription_onData_closure, A.BrowserWebSocket_connect_closure, A.BrowserWebSocket_connect_closure0, A.BrowserWebSocket_connect_closure1, A.BrowserWebSocket_connect_closure2, A.AdapterWebSocketChannel_closure, A.AdapterWebSocketChannel__closure, A.AdapterWebSocketChannel__closure0, A.AdapterWebSocketChannel_closure0, A.main__closure1, A.main__closure3, A.main___closure2, A.main___closure1, A.main__closure5, A.main___closure0, A.main___closure, A.main__closure7, A.main__closure8, A.main__closure9, A._sendConnectRequest_closure, A._launchCommunicationWithDebugExtension_closure, A._handleAuthRequest_closure, A._sendResponse_closure, A.DdcLibraryBundleRestarter_restart_closure, A.DdcRestarter_restart_closure0, A.DdcRestarter_restart_closure, A.RequireRestarter__reloadModule_closure0, A.JSArrayExtension_toDartIterable_closure]); + _inheritMany(A.Closure, [A.Closure2Args, A.Closure0Args, A.Instantiation, A.TearOffClosure, A.initHooks_closure, A.initHooks_closure1, A._AsyncRun__initializeScheduleImmediate_internalCallback, A._AsyncRun__initializeScheduleImmediate_closure, A._awaitOnObject_closure, A._Future__propagateToListeners_handleWhenCompleteCallback_closure, A._Future_timeout_closure0, A.Stream_length_closure, A.Stream_first_closure0, A._CustomZone_bindUnaryCallback_closure, A._CustomZone_bindUnaryCallbackGuarded_closure, A._RootZone_bindUnaryCallback_closure, A._RootZone_bindUnaryCallbackGuarded_closure, A.runZonedGuarded_closure, A._CustomHashMap_closure, A._LinkedCustomHashMap_closure, A._BigIntImpl_hashCode_finish, A._Uri__makePath_closure, A.FutureOfJSAnyToJSPromise_get_toJS__closure, A.FutureOfVoidToJSPromise_get_toJS__closure, A.jsify__convert, A.promiseToFuture_closure, A.promiseToFuture_closure0, A.dartify_convert, A.StreamQueue__ensureListening_closure, A.BuiltListMultimap_BuiltListMultimap_closure, A.BuiltListMultimap_hashCode_closure, A.ListMultimapBuilder_replace_closure, A.BuiltMap_BuiltMap_closure, A.BuiltMap_hashCode_closure, A.BuiltSet_hashCode_closure, A.BuiltSetMultimap_hashCode_closure, A.SetMultimapBuilder_replace_closure, A.newBuiltValueToStringHelper_closure, A.BuiltListMultimapSerializer_serialize_closure, A.BuiltListMultimapSerializer_deserialize_closure, A.BuiltListSerializer_serialize_closure, A.BuiltListSerializer_deserialize_closure, A.BuiltSetMultimapSerializer_serialize_closure, A.BuiltSetMultimapSerializer_deserialize_closure, A.BuiltSetSerializer_serialize_closure, A.BuiltSetSerializer_deserialize_closure, A.CanonicalizedMap_keys_closure, A.ServiceExtensionResponse_ServiceExtensionResponse$fromResult_closure, A.WebSocketClient_stream_closure, A.BaseRequest_closure0, A.BrowserClient_send_closure, A.BrowserClient_send_closure0, A.ByteStream_toBytes_closure, A.MediaType_toString__closure, A.expectQuotedString_closure, A.Context_joinAll_closure, A.Context_split_closure, A._validateArgList_closure, A.Pool__runOnRelease_closure, A.Highlighter$__closure, A.Highlighter$___closure, A.Highlighter$__closure0, A.Highlighter__collateLines_closure, A.Highlighter__collateLines_closure1, A.Highlighter__collateLines__closure, A.Highlighter_highlight_closure, A.SseClient_closure0, A.SseClient_closure1, A.generateUuidV4_generateBits, A._GuaranteeSink__addError_closure, A._EventStreamSubscription_closure, A._EventStreamSubscription_onData_closure, A.BrowserWebSocket_connect_closure, A.BrowserWebSocket_connect_closure0, A.BrowserWebSocket_connect_closure1, A.BrowserWebSocket_connect_closure2, A.AdapterWebSocketChannel_closure, A.AdapterWebSocketChannel__closure, A.AdapterWebSocketChannel__closure0, A.AdapterWebSocketChannel_closure0, A.main__closure1, A.main__closure3, A.main___closure2, A.main___closure1, A.main__closure5, A.main___closure0, A.main___closure, A.main__closure7, A.main__closure8, A.main__closure9, A._sendConnectRequest_closure, A._launchCommunicationWithDebugExtension_closure, A._handleAuthRequest_closure, A._sendResponse_closure, A.DdcLibraryBundleRestarter_restart_closure, A.DdcRestarter_restart_closure0, A.DdcRestarter_restart_closure, A.RequireRestarter__reloadModule_closure0, A.JSArrayExtension_toDartIterable_closure]); _inheritMany(A.Closure2Args, [A._CastListBase_sort_closure, A.CastMap_forEach_closure, A.ConstantMap_map_closure, A.JsLinkedHashMap_addAll_closure, A.initHooks_closure0, A._awaitOnObject_closure0, A._wrapJsFunctionForAsync_closure, A._Future__propagateToListeners_handleWhenCompleteCallback_closure0, A._Future_timeout_closure1, A._BufferingStreamSubscription_asFuture_closure0, A.LinkedHashMap_LinkedHashMap$from_closure, A.MapBase_mapToString_closure, A._JsonStringifier_writeMap_closure, A._BigIntImpl_hashCode_combine, A.Uri__parseIPv4Address_error, A.Uri_parseIPv6Address_error, A.Uri_parseIPv6Address_parseHex, A.FutureOfJSAnyToJSPromise_get_toJS_closure, A.FutureOfJSAnyToJSPromise_get_toJS__closure0, A.FutureOfVoidToJSPromise_get_toJS_closure, A.FutureOfVoidToJSPromise_get_toJS__closure0, A.StreamQueue__ensureListening_closure1, A.hashObjects_closure, A.MapBuilder_replace_closure, A.CanonicalizedMap_addAll_closure, A.CanonicalizedMap_forEach_closure, A.CanonicalizedMap_map_closure, A.safeUnawaited_closure, A.BaseRequest_closure, A.MediaType_toString_closure, A.Pool__runOnRelease_closure0, A.Highlighter__collateLines_closure0, A.generateUuidV4_printDigits, A.generateUuidV4_bitsDigits, A.main__closure4, A.main_closure0]); _inherit(A.CastList, A._CastListBase); _inheritMany(A.MapBase, [A.CastMap, A.JsLinkedHashMap, A._HashMap, A._JsonMap]); @@ -28094,6 +28636,8 @@ _inherit(A._$IsolateStart, A.IsolateStart); _inherit(A._$RegisterEvent, A.RegisterEvent); _inherit(A._$RunRequest, A.RunRequest); + _inherit(A._$ServiceExtensionRequest, A.ServiceExtensionRequest); + _inherit(A._$ServiceExtensionResponse, A.ServiceExtensionResponse); _inheritMany(A.SocketClient, [A.SseSocketClient, A.WebSocketClient]); _inherit(A.BrowserClient, A.BaseClient); _inherit(A.ByteStream, A.StreamView); @@ -28131,7 +28675,7 @@ typeUniverse: {eC: new Map(), tR: {}, eT: {}, tPV: {}, sEA: []}, mangledGlobalNames: {int: "int", double: "double", num: "num", String: "String", bool: "bool", Null: "Null", List: "List", Object: "Object", Map: "Map"}, mangledNames: {}, - types: ["~()", "Null()", "~(JSObject)", "Object?(@)", "@(@)", "Null(Object,StackTrace)", "Null(@)", "~(@)", "Null(JSObject)", "~(Object?)", "JSObject()", "Object?(Object?)", "bool(Object?)", "String(String)", "~(Object,StackTrace)", "bool(_Highlight)", "~(~())", "int(Object?)", "bool(Object?,Object?)", "Future<~>()", "String(int,int)", "~(@,StackTrace)", "int()", "~(@,@)", "~(Object?,Object?)", "@()", "int(int,int)", "int(int)", "int(@,@)", "Null(JavaScriptFunction,JavaScriptFunction)", "bool(String)", "String(Match)", "~(Object[StackTrace?])", "bool()", "MapBuilder()", "SetBuilder()", "ListMultimapBuilder()", "~(int,@)", "ListBuilder()", "IndentingBuiltValueToStringHelper(String)", "ListBuilder()", "ListBuilder()", "int(int,@)", "String(@)", "bool(String,String)", "int(String)", "Object?(~)", "~(List)", "MediaType()", "~(String,String)", "JSObject(Object,StackTrace)", "Logger()", "Null(@,StackTrace)", "~(String,int?)", "String?()", "int(_Line)", "~(String,int)", "Object(_Line)", "Object(_Highlight)", "int(_Highlight,_Highlight)", "List<_Line>(MapEntry>)", "Null(~())", "SourceSpanWithContext()", "~(Zone,ZoneDelegate,Zone,Object,StackTrace)", "~(String?)", "Future()", "@(@,String)", "Null(WebSocket)", "~(WebSocketEvent)", "Null(Object)", "HotReloadResponse([~(HotReloadResponseBuilder)])", "JSObject(String[bool?])", "~(List)", "ListBuilder(BatchedDebugEventsBuilder)", "Null(String,String)", "DebugEventBuilder(DebugEventBuilder)", "Null(String)", "RegisterEventBuilder(RegisterEventBuilder)", "DevToolsRequestBuilder(DevToolsRequestBuilder)", "Future<~>(String)", "ConnectRequestBuilder(ConnectRequestBuilder)", "DebugInfoBuilder(DebugInfoBuilder)", "~(bool)", "Null(JavaScriptFunction)", "bool(bool)", "List(String)", "int(String,String)", "Null(JavaScriptObject)", "JSObject()()", "SetMultimapBuilder()", "@(String)", "~(Zone?,ZoneDelegate?,Zone,Object,StackTrace)", "0^(Zone?,ZoneDelegate?,Zone,0^())", "0^(Zone?,ZoneDelegate?,Zone,0^(1^),1^)", "0^(Zone?,ZoneDelegate?,Zone,0^(1^,2^),1^,2^)", "0^()(Zone,ZoneDelegate,Zone,0^())", "0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))", "0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))", "AsyncError?(Zone,ZoneDelegate,Zone,Object,StackTrace?)", "~(Zone?,ZoneDelegate?,Zone,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))", "~(Zone,ZoneDelegate,Zone,String)", "~(String)", "Zone(Zone?,ZoneDelegate?,Zone,ZoneSpecification?,Map?)", "0^(0^,0^)", "String(String?)"], + types: ["~()", "Null()", "~(JSObject)", "Object?(@)", "@(@)", "Null(Object,StackTrace)", "Null(@)", "~(@)", "Null(JSObject)", "~(Object?)", "JSObject()", "Object?(Object?)", "~(~())", "~(Object,StackTrace)", "bool(Object?)", "String(String)", "Future<~>()", "bool(Object?,Object?)", "int(Object?)", "bool(_Highlight)", "bool(String)", "@()", "int(int)", "~(Object[StackTrace?])", "Null(JavaScriptFunction,JavaScriptFunction)", "~(@,StackTrace)", "bool()", "int(int,int)", "String(Match)", "~(@,@)", "int()", "~(Object?,Object?)", "String(int,int)", "int(@,@)", "MediaType()", "@(@,String)", "SetMultimapBuilder()", "Null(~())", "~(String,int)", "~(Zone,ZoneDelegate,Zone,Object,StackTrace)", "ListBuilder()", "ListBuilder()", "~(ServiceExtensionResponseBuilder)", "~(String,int?)", "String(@)", "bool(String,String)", "int(String)", "Null(@,StackTrace)", "~(List)", "@(String)", "~(String,String)", "JSObject(Object,StackTrace)", "Logger()", "Object?(~)", "String(String?)", "String?()", "int(_Line)", "~(int,@)", "Object(_Line)", "Object(_Highlight)", "int(_Highlight,_Highlight)", "List<_Line>(MapEntry>)", "int(int,@)", "SourceSpanWithContext()", "IndentingBuiltValueToStringHelper(String)", "~(String?)", "Future()", "ListBuilder()", "Null(WebSocket)", "~(WebSocketEvent)", "Null(Object)", "HotReloadResponse([~(HotReloadResponseBuilder)])", "JSObject(String[bool?])", "~(List)", "ListBuilder(BatchedDebugEventsBuilder)", "Null(String,String)", "DebugEventBuilder(DebugEventBuilder)", "0^(0^,0^)", "RegisterEventBuilder(RegisterEventBuilder)", "DevToolsRequestBuilder(DevToolsRequestBuilder)", "Future<~>(String)", "ConnectRequestBuilder(ConnectRequestBuilder)", "DebugInfoBuilder(DebugInfoBuilder)", "~(bool)", "Null(JavaScriptFunction)", "bool(bool)", "List(String)", "int(String,String)", "Null(JavaScriptObject)", "JSObject()()", "ListMultimapBuilder()", "MapBuilder()", "~(Zone?,ZoneDelegate?,Zone,Object,StackTrace)", "0^(Zone?,ZoneDelegate?,Zone,0^())", "0^(Zone?,ZoneDelegate?,Zone,0^(1^),1^)", "0^(Zone?,ZoneDelegate?,Zone,0^(1^,2^),1^,2^)", "0^()(Zone,ZoneDelegate,Zone,0^())", "0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))", "0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))", "AsyncError?(Zone,ZoneDelegate,Zone,Object,StackTrace?)", "~(Zone?,ZoneDelegate?,Zone,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))", "~(Zone,ZoneDelegate,Zone,String)", "~(String)", "Zone(Zone?,ZoneDelegate?,Zone,ZoneSpecification?,Map?)", "Null(String)", "SetBuilder()"], interceptorsByTag: null, leafTags: null, arrayRti: Symbol("$ti"), @@ -28139,7 +28683,7 @@ "2;libraries,sources": (t1, t2) => o => o instanceof A._Record_2_libraries_sources && t1._is(o._0) && t2._is(o._1) } }; - A._Universe_addRules(init.typeUniverse, JSON.parse('{"JavaScriptFunction":"LegacyJavaScriptObject","PlainJavaScriptObject":"LegacyJavaScriptObject","UnknownJavaScriptObject":"LegacyJavaScriptObject","JavaScriptObject":{"JSObject":[]},"JSArray":{"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"JSObject":[],"Iterable":["1"],"JSIndexable":["1"],"Iterable.E":"1"},"JSBool":{"bool":[],"TrustedGetRuntimeType":[]},"JSNull":{"Null":[],"TrustedGetRuntimeType":[]},"LegacyJavaScriptObject":{"JavaScriptObject":[],"JSObject":[]},"JSUnmodifiableArray":{"JSArray":["1"],"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"JSObject":[],"Iterable":["1"],"JSIndexable":["1"],"Iterable.E":"1"},"ArrayIterator":{"Iterator":["1"]},"JSNumber":{"double":[],"num":[],"Comparable":["num"]},"JSInt":{"double":[],"int":[],"num":[],"Comparable":["num"],"TrustedGetRuntimeType":[]},"JSNumNotInt":{"double":[],"num":[],"Comparable":["num"],"TrustedGetRuntimeType":[]},"JSString":{"String":[],"Comparable":["String"],"Pattern":[],"JSIndexable":["@"],"TrustedGetRuntimeType":[]},"_CastIterableBase":{"Iterable":["2"]},"CastIterator":{"Iterator":["2"]},"CastIterable":{"_CastIterableBase":["1","2"],"Iterable":["2"],"Iterable.E":"2"},"_EfficientLengthCastIterable":{"CastIterable":["1","2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"_CastListBase":{"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"]},"CastList":{"_CastListBase":["1","2"],"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListBase.E":"2","Iterable.E":"2"},"CastMap":{"MapBase":["3","4"],"Map":["3","4"],"MapBase.K":"3","MapBase.V":"4"},"LateError":{"Error":[]},"CodeUnits":{"ListBase":["int"],"UnmodifiableListMixin":["int"],"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"],"ListBase.E":"int","Iterable.E":"int","UnmodifiableListMixin.E":"int"},"EfficientLengthIterable":{"Iterable":["1"]},"ListIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"SubListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"ListIterator":{"Iterator":["1"]},"MappedIterable":{"Iterable":["2"],"Iterable.E":"2"},"EfficientLengthMappedIterable":{"MappedIterable":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"MappedIterator":{"Iterator":["2"]},"MappedListIterable":{"ListIterable":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListIterable.E":"2","Iterable.E":"2"},"WhereIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereIterator":{"Iterator":["1"]},"ExpandIterable":{"Iterable":["2"],"Iterable.E":"2"},"ExpandIterator":{"Iterator":["2"]},"TakeIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthTakeIterable":{"TakeIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"TakeIterator":{"Iterator":["1"]},"SkipIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthSkipIterable":{"SkipIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"SkipIterator":{"Iterator":["1"]},"EmptyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"EmptyIterator":{"Iterator":["1"]},"WhereTypeIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereTypeIterator":{"Iterator":["1"]},"UnmodifiableListBase":{"ListBase":["1"],"UnmodifiableListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ReversedListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"_Record_2_libraries_sources":{"_Record2":[],"_Record":[]},"ConstantMap":{"Map":["1","2"]},"ConstantStringMap":{"ConstantMap":["1","2"],"Map":["1","2"]},"_KeysOrValues":{"Iterable":["1"],"Iterable.E":"1"},"_KeysOrValuesOrElementsIterator":{"Iterator":["1"]},"Instantiation":{"Closure":[],"Function":[]},"Instantiation1":{"Closure":[],"Function":[]},"NullError":{"TypeError":[],"Error":[]},"JsNoSuchMethodError":{"Error":[]},"UnknownJsTypeError":{"Error":[]},"NullThrownFromJavaScriptException":{"Exception":[]},"_StackTrace":{"StackTrace":[]},"Closure":{"Function":[]},"Closure0Args":{"Closure":[],"Function":[]},"Closure2Args":{"Closure":[],"Function":[]},"TearOffClosure":{"Closure":[],"Function":[]},"StaticClosure":{"Closure":[],"Function":[]},"BoundClosure":{"Closure":[],"Function":[]},"RuntimeError":{"Error":[]},"JsLinkedHashMap":{"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"LinkedHashMapKeysIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapKeyIterator":{"Iterator":["1"]},"LinkedHashMapValuesIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapValueIterator":{"Iterator":["1"]},"LinkedHashMapEntriesIterable":{"EfficientLengthIterable":["MapEntry<1,2>"],"Iterable":["MapEntry<1,2>"],"Iterable.E":"MapEntry<1,2>"},"LinkedHashMapEntryIterator":{"Iterator":["MapEntry<1,2>"]},"JsIdentityLinkedHashMap":{"JsLinkedHashMap":["1","2"],"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_Record2":{"_Record":[]},"JSSyntaxRegExp":{"RegExp":[],"Pattern":[]},"_MatchImplementation":{"RegExpMatch":[],"Match":[]},"_AllMatchesIterable":{"Iterable":["RegExpMatch"],"Iterable.E":"RegExpMatch"},"_AllMatchesIterator":{"Iterator":["RegExpMatch"]},"StringMatch":{"Match":[]},"_StringAllMatchesIterable":{"Iterable":["Match"],"Iterable.E":"Match"},"_StringAllMatchesIterator":{"Iterator":["Match"]},"NativeByteBuffer":{"JavaScriptObject":[],"JSObject":[],"ByteBuffer":[],"TrustedGetRuntimeType":[]},"NativeTypedData":{"JavaScriptObject":[],"JSObject":[]},"_UnmodifiableNativeByteBufferView":{"ByteBuffer":[]},"NativeByteData":{"JavaScriptObject":[],"ByteData":[],"JSObject":[],"TrustedGetRuntimeType":[]},"NativeTypedArray":{"JavaScriptIndexingBehavior":["1"],"JavaScriptObject":[],"JSObject":[],"JSIndexable":["1"]},"NativeTypedArrayOfDouble":{"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"]},"NativeTypedArrayOfInt":{"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"]},"NativeFloat32List":{"Float32List":[],"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double","Iterable.E":"double","FixedLengthListMixin.E":"double"},"NativeFloat64List":{"Float64List":[],"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double","Iterable.E":"double","FixedLengthListMixin.E":"double"},"NativeInt16List":{"NativeTypedArrayOfInt":[],"Int16List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeInt32List":{"NativeTypedArrayOfInt":[],"Int32List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeInt8List":{"NativeTypedArrayOfInt":[],"Int8List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint16List":{"NativeTypedArrayOfInt":[],"Uint16List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint32List":{"NativeTypedArrayOfInt":[],"Uint32List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint8ClampedList":{"NativeTypedArrayOfInt":[],"Uint8ClampedList":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint8List":{"NativeTypedArrayOfInt":[],"Uint8List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"_Type":{"Type":[]},"_Error":{"Error":[]},"_TypeError":{"TypeError":[],"Error":[]},"AsyncError":{"Error":[]},"_TimerImpl":{"Timer":[]},"_AsyncAwaitCompleter":{"Completer":["1"]},"_Completer":{"Completer":["1"]},"_AsyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_SyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_Future":{"Future":["1"]},"StreamView":{"Stream":["1"]},"_StreamController":{"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_AsyncStreamController":{"_AsyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_SyncStreamController":{"_SyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_ControllerStream":{"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_ControllerSubscription":{"_BufferingStreamSubscription":["1"],"StreamSubscription":["1"],"_EventSink":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamSinkWrapper":{"StreamSink":["1"]},"_BufferingStreamSubscription":{"StreamSubscription":["1"],"_EventSink":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamImpl":{"Stream":["1"]},"_DelayedData":{"_DelayedEvent":["1"]},"_DelayedError":{"_DelayedEvent":["@"]},"_DelayedDone":{"_DelayedEvent":["@"]},"_DoneStreamSubscription":{"StreamSubscription":["1"]},"_EmptyStream":{"Stream":["1"],"Stream.T":"1"},"_ForwardingStream":{"Stream":["2"]},"_ForwardingStreamSubscription":{"_BufferingStreamSubscription":["2"],"StreamSubscription":["2"],"_EventSink":["2"],"_EventDispatch":["2"],"_BufferingStreamSubscription.T":"2"},"_MapStream":{"_ForwardingStream":["1","2"],"Stream":["2"],"Stream.T":"2"},"_ZoneSpecification":{"ZoneSpecification":[]},"_ZoneDelegate":{"ZoneDelegate":[]},"_Zone":{"Zone":[]},"_CustomZone":{"_Zone":[],"Zone":[]},"_RootZone":{"_Zone":[],"Zone":[]},"_SplayTreeSetNode":{"_SplayTreeNode":["1","_SplayTreeSetNode<1>"],"_SplayTreeNode.K":"1","_SplayTreeNode.1":"_SplayTreeSetNode<1>"},"_HashMap":{"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_IdentityHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_CustomHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashMapKeyIterator":{"Iterator":["1"]},"_LinkedCustomHashMap":{"JsLinkedHashMap":["1","2"],"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashSet":{"_SetBase":["1"],"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashSetIterator":{"Iterator":["1"]},"_LinkedHashSet":{"_SetBase":["1"],"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_LinkedHashSetIterator":{"Iterator":["1"]},"UnmodifiableListView":{"ListBase":["1"],"UnmodifiableListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListBase.E":"1","Iterable.E":"1","UnmodifiableListMixin.E":"1"},"ListBase":{"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"MapBase":{"Map":["1","2"]},"MapView":{"Map":["1","2"]},"UnmodifiableMapView":{"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ListQueue":{"Queue":["1"],"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"_ListQueueIterator":{"Iterator":["1"]},"SetBase":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SetBase":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SplayTreeIterator":{"Iterator":["3"]},"_SplayTreeKeyIterator":{"_SplayTreeIterator":["1","2","1"],"Iterator":["1"],"_SplayTreeIterator.K":"1","_SplayTreeIterator.T":"1","_SplayTreeIterator.1":"2"},"SplayTreeSet":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"_SplayTree":["1","_SplayTreeSetNode<1>"],"Iterable":["1"],"Iterable.E":"1","_SplayTree.1":"_SplayTreeSetNode<1>","_SplayTree.K":"1"},"Encoding":{"Codec":["String","List"]},"_JsonMap":{"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"_JsonMapKeyIterable":{"ListIterable":["String"],"EfficientLengthIterable":["String"],"Iterable":["String"],"ListIterable.E":"String","Iterable.E":"String"},"AsciiCodec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"_UnicodeSubsetEncoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"AsciiEncoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"_UnicodeSubsetDecoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"AsciiDecoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Base64Codec":{"Codec":["List","String"],"Codec.S":"List"},"Base64Encoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Base64Decoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Converter":{"StreamTransformer":["1","2"]},"JsonUnsupportedObjectError":{"Error":[]},"JsonCyclicError":{"Error":[]},"JsonCodec":{"Codec":["Object?","String"],"Codec.S":"Object?"},"JsonEncoder":{"Converter":["Object?","String"],"StreamTransformer":["Object?","String"]},"JsonDecoder":{"Converter":["String","Object?"],"StreamTransformer":["String","Object?"]},"Latin1Codec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"Latin1Encoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Latin1Decoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Utf8Codec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"Utf8Encoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Utf8Decoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"BigInt":{"Comparable":["BigInt"]},"DateTime":{"Comparable":["DateTime"]},"double":{"num":[],"Comparable":["num"]},"Duration":{"Comparable":["Duration"]},"int":{"num":[],"Comparable":["num"]},"List":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"num":{"Comparable":["num"]},"RegExp":{"Pattern":[]},"RegExpMatch":{"Match":[]},"Set":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"String":{"Comparable":["String"],"Pattern":[]},"_BigIntImpl":{"BigInt":[],"Comparable":["BigInt"]},"AssertionError":{"Error":[]},"TypeError":{"Error":[]},"ArgumentError":{"Error":[]},"RangeError":{"Error":[]},"IndexError":{"Error":[]},"UnsupportedError":{"Error":[]},"UnimplementedError":{"Error":[]},"StateError":{"Error":[]},"ConcurrentModificationError":{"Error":[]},"OutOfMemoryError":{"Error":[]},"StackOverflowError":{"Error":[]},"_Exception":{"Exception":[]},"FormatException":{"Exception":[]},"IntegerDivisionByZeroException":{"Exception":[],"Error":[]},"_StringStackTrace":{"StackTrace":[]},"StringBuffer":{"StringSink":[]},"_Uri":{"Uri":[]},"_SimpleUri":{"Uri":[]},"_DataUri":{"Uri":[]},"NullRejectionException":{"Exception":[]},"DelegatingStreamSink":{"StreamSink":["1"]},"ErrorResult":{"Result":["0&"]},"ValueResult":{"Result":["1"]},"_NextRequest":{"_EventRequest":["1"]},"_HasNextRequest":{"_EventRequest":["1"]},"BuiltList":{"Iterable":["1"]},"_BuiltList":{"BuiltList":["1"],"Iterable":["1"],"Iterable.E":"1"},"_BuiltListMultimap":{"BuiltListMultimap":["1","2"]},"_BuiltMap":{"BuiltMap":["1","2"]},"BuiltSet":{"Iterable":["1"]},"_BuiltSet":{"BuiltSet":["1"],"Iterable":["1"],"Iterable.E":"1"},"_BuiltSetMultimap":{"BuiltSetMultimap":["1","2"]},"BuiltValueNullFieldError":{"Error":[]},"BuiltValueNestedFieldError":{"Error":[]},"BoolJsonObject":{"JsonObject":[]},"ListJsonObject":{"JsonObject":[]},"MapJsonObject":{"JsonObject":[]},"NumJsonObject":{"JsonObject":[]},"StringJsonObject":{"JsonObject":[]},"DeserializationError":{"Error":[]},"BigIntSerializer":{"PrimitiveSerializer":["BigInt"],"Serializer":["BigInt"]},"BoolSerializer":{"PrimitiveSerializer":["bool"],"Serializer":["bool"]},"BuiltJsonSerializers":{"Serializers":[]},"BuiltListMultimapSerializer":{"StructuredSerializer":["BuiltListMultimap<@,@>"],"Serializer":["BuiltListMultimap<@,@>"]},"BuiltListSerializer":{"StructuredSerializer":["BuiltList<@>"],"Serializer":["BuiltList<@>"]},"BuiltMapSerializer":{"StructuredSerializer":["BuiltMap<@,@>"],"Serializer":["BuiltMap<@,@>"]},"BuiltSetMultimapSerializer":{"StructuredSerializer":["BuiltSetMultimap<@,@>"],"Serializer":["BuiltSetMultimap<@,@>"]},"BuiltSetSerializer":{"StructuredSerializer":["BuiltSet<@>"],"Serializer":["BuiltSet<@>"]},"DateTimeSerializer":{"PrimitiveSerializer":["DateTime"],"Serializer":["DateTime"]},"DoubleSerializer":{"PrimitiveSerializer":["double"],"Serializer":["double"]},"DurationSerializer":{"PrimitiveSerializer":["Duration"],"Serializer":["Duration"]},"Int32Serializer":{"PrimitiveSerializer":["Int32"],"Serializer":["Int32"]},"Int64Serializer":{"PrimitiveSerializer":["Int64"],"Serializer":["Int64"]},"IntSerializer":{"PrimitiveSerializer":["int"],"Serializer":["int"]},"JsonObjectSerializer":{"PrimitiveSerializer":["JsonObject"],"Serializer":["JsonObject"]},"NullSerializer":{"PrimitiveSerializer":["Null"],"Serializer":["Null"]},"NumSerializer":{"PrimitiveSerializer":["num"],"Serializer":["num"]},"RegExpSerializer":{"PrimitiveSerializer":["RegExp"],"Serializer":["RegExp"]},"StringSerializer":{"PrimitiveSerializer":["String"],"Serializer":["String"]},"Uint8ListSerializer":{"PrimitiveSerializer":["Uint8List"],"Serializer":["Uint8List"]},"UriSerializer":{"PrimitiveSerializer":["Uri"],"Serializer":["Uri"]},"CanonicalizedMap":{"Map":["2","3"]},"DefaultEquality":{"Equality":["1"]},"IterableEquality":{"Equality":["Iterable<1>"]},"ListEquality":{"Equality":["List<1>"]},"_UnorderedEquality":{"Equality":["2"]},"SetEquality":{"_UnorderedEquality":["1","Set<1>"],"Equality":["Set<1>"],"_UnorderedEquality.E":"1","_UnorderedEquality.T":"Set<1>"},"MapEquality":{"Equality":["Map<1,2>"]},"DeepCollectionEquality":{"Equality":["@"]},"QueueList":{"ListBase":["1"],"List":["1"],"Queue":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListBase.E":"1","QueueList.E":"1","Iterable.E":"1"},"_CastQueueList":{"QueueList":["2"],"ListBase":["2"],"List":["2"],"Queue":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListBase.E":"2","QueueList.E":"2","Iterable.E":"2"},"_$BuildStatusSerializer":{"PrimitiveSerializer":["BuildStatus"],"Serializer":["BuildStatus"]},"_$BuildResultSerializer":{"StructuredSerializer":["BuildResult"],"Serializer":["BuildResult"]},"_$BuildResult":{"BuildResult":[]},"_$ConnectRequestSerializer":{"StructuredSerializer":["ConnectRequest"],"Serializer":["ConnectRequest"]},"_$ConnectRequest":{"ConnectRequest":[]},"_$DebugEventSerializer":{"StructuredSerializer":["DebugEvent"],"Serializer":["DebugEvent"]},"_$BatchedDebugEventsSerializer":{"StructuredSerializer":["BatchedDebugEvents"],"Serializer":["BatchedDebugEvents"]},"_$DebugEvent":{"DebugEvent":[]},"_$BatchedDebugEvents":{"BatchedDebugEvents":[]},"_$DebugInfoSerializer":{"StructuredSerializer":["DebugInfo"],"Serializer":["DebugInfo"]},"_$DebugInfo":{"DebugInfo":[]},"_$DevToolsRequestSerializer":{"StructuredSerializer":["DevToolsRequest"],"Serializer":["DevToolsRequest"]},"_$DevToolsResponseSerializer":{"StructuredSerializer":["DevToolsResponse"],"Serializer":["DevToolsResponse"]},"_$DevToolsRequest":{"DevToolsRequest":[]},"_$DevToolsResponse":{"DevToolsResponse":[]},"_$ErrorResponseSerializer":{"StructuredSerializer":["ErrorResponse"],"Serializer":["ErrorResponse"]},"_$ErrorResponse":{"ErrorResponse":[]},"_$ExtensionRequestSerializer":{"StructuredSerializer":["ExtensionRequest"],"Serializer":["ExtensionRequest"]},"_$ExtensionResponseSerializer":{"StructuredSerializer":["ExtensionResponse"],"Serializer":["ExtensionResponse"]},"_$ExtensionEventSerializer":{"StructuredSerializer":["ExtensionEvent"],"Serializer":["ExtensionEvent"]},"_$BatchedEventsSerializer":{"StructuredSerializer":["BatchedEvents"],"Serializer":["BatchedEvents"]},"_$ExtensionRequest":{"ExtensionRequest":[]},"_$ExtensionResponse":{"ExtensionResponse":[]},"_$ExtensionEvent":{"ExtensionEvent":[]},"_$BatchedEvents":{"BatchedEvents":[]},"_$HotReloadRequestSerializer":{"StructuredSerializer":["HotReloadRequest"],"Serializer":["HotReloadRequest"]},"_$HotReloadRequest":{"HotReloadRequest":[]},"_$HotReloadResponseSerializer":{"StructuredSerializer":["HotReloadResponse"],"Serializer":["HotReloadResponse"]},"_$HotReloadResponse":{"HotReloadResponse":[]},"_$IsolateExitSerializer":{"StructuredSerializer":["IsolateExit"],"Serializer":["IsolateExit"]},"_$IsolateStartSerializer":{"StructuredSerializer":["IsolateStart"],"Serializer":["IsolateStart"]},"_$IsolateExit":{"IsolateExit":[]},"_$IsolateStart":{"IsolateStart":[]},"_$RegisterEventSerializer":{"StructuredSerializer":["RegisterEvent"],"Serializer":["RegisterEvent"]},"_$RegisterEvent":{"RegisterEvent":[]},"_$RunRequestSerializer":{"StructuredSerializer":["RunRequest"],"Serializer":["RunRequest"]},"_$RunRequest":{"RunRequest":[]},"SseSocketClient":{"SocketClient":[]},"WebSocketClient":{"SocketClient":[]},"Int32":{"Comparable":["Object"]},"Int64":{"Comparable":["Object"]},"ByteStream":{"StreamView":["List"],"Stream":["List"],"Stream.T":"List","StreamView.T":"List"},"ClientException":{"Exception":[]},"Request":{"BaseRequest":[]},"StreamedResponseV2":{"StreamedResponse":[]},"CaseInsensitiveMap":{"CanonicalizedMap":["String","String","1"],"Map":["String","1"],"CanonicalizedMap.K":"String","CanonicalizedMap.V":"1","CanonicalizedMap.C":"String"},"Level":{"Comparable":["Level"]},"PathException":{"Exception":[]},"PosixStyle":{"InternalStyle":[]},"UrlStyle":{"InternalStyle":[]},"WindowsStyle":{"InternalStyle":[]},"FileLocation":{"SourceLocation":[],"Comparable":["SourceLocation"]},"_FileSpan":{"SourceSpanWithContext":[],"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceLocation":{"Comparable":["SourceLocation"]},"SourceLocationMixin":{"SourceLocation":[],"Comparable":["SourceLocation"]},"SourceSpan":{"Comparable":["SourceSpan"]},"SourceSpanBase":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceSpanException":{"Exception":[]},"SourceSpanFormatException":{"FormatException":[],"Exception":[]},"SourceSpanMixin":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceSpanWithContext":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SseClient":{"StreamChannel":["String?"]},"GuaranteeChannel":{"StreamChannel":["1"]},"_GuaranteeSink":{"StreamSink":["1"]},"StreamChannelMixin":{"StreamChannel":["1"]},"StringScannerException":{"FormatException":[],"Exception":[]},"_EventStream":{"Stream":["1"],"Stream.T":"1"},"_EventStreamSubscription":{"StreamSubscription":["1"]},"BrowserWebSocket":{"WebSocket":[]},"TextDataReceived":{"WebSocketEvent":[]},"BinaryDataReceived":{"WebSocketEvent":[]},"CloseReceived":{"WebSocketEvent":[]},"WebSocketException":{"Exception":[]},"WebSocketConnectionClosed":{"Exception":[]},"AdapterWebSocketChannel":{"WebSocketChannel":[],"StreamChannel":["@"]},"_WebSocketSink":{"WebSocketSink":[],"DelegatingStreamSink":["@"],"StreamSink":["@"],"DelegatingStreamSink.T":"@"},"WebSocketChannelException":{"Exception":[]},"DdcLibraryBundleRestarter":{"Restarter":[]},"DdcRestarter":{"Restarter":[]},"RequireRestarter":{"Restarter":[]},"HotReloadFailedException":{"Exception":[]},"Int8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8ClampedList":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Float32List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]},"Float64List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]}}')); + A._Universe_addRules(init.typeUniverse, JSON.parse('{"JavaScriptFunction":"LegacyJavaScriptObject","PlainJavaScriptObject":"LegacyJavaScriptObject","UnknownJavaScriptObject":"LegacyJavaScriptObject","JavaScriptObject":{"JSObject":[]},"JSArray":{"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"JSObject":[],"Iterable":["1"],"JSIndexable":["1"],"Iterable.E":"1"},"JSBool":{"bool":[],"TrustedGetRuntimeType":[]},"JSNull":{"Null":[],"TrustedGetRuntimeType":[]},"LegacyJavaScriptObject":{"JavaScriptObject":[],"JSObject":[]},"JSUnmodifiableArray":{"JSArray":["1"],"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"JSObject":[],"Iterable":["1"],"JSIndexable":["1"],"Iterable.E":"1"},"ArrayIterator":{"Iterator":["1"]},"JSNumber":{"double":[],"num":[],"Comparable":["num"]},"JSInt":{"double":[],"int":[],"num":[],"Comparable":["num"],"TrustedGetRuntimeType":[]},"JSNumNotInt":{"double":[],"num":[],"Comparable":["num"],"TrustedGetRuntimeType":[]},"JSString":{"String":[],"Comparable":["String"],"Pattern":[],"JSIndexable":["@"],"TrustedGetRuntimeType":[]},"_CastIterableBase":{"Iterable":["2"]},"CastIterator":{"Iterator":["2"]},"CastIterable":{"_CastIterableBase":["1","2"],"Iterable":["2"],"Iterable.E":"2"},"_EfficientLengthCastIterable":{"CastIterable":["1","2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"_CastListBase":{"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"]},"CastList":{"_CastListBase":["1","2"],"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListBase.E":"2","Iterable.E":"2"},"CastMap":{"MapBase":["3","4"],"Map":["3","4"],"MapBase.K":"3","MapBase.V":"4"},"LateError":{"Error":[]},"CodeUnits":{"ListBase":["int"],"UnmodifiableListMixin":["int"],"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"],"ListBase.E":"int","Iterable.E":"int","UnmodifiableListMixin.E":"int"},"EfficientLengthIterable":{"Iterable":["1"]},"ListIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"SubListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"ListIterator":{"Iterator":["1"]},"MappedIterable":{"Iterable":["2"],"Iterable.E":"2"},"EfficientLengthMappedIterable":{"MappedIterable":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"MappedIterator":{"Iterator":["2"]},"MappedListIterable":{"ListIterable":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListIterable.E":"2","Iterable.E":"2"},"WhereIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereIterator":{"Iterator":["1"]},"ExpandIterable":{"Iterable":["2"],"Iterable.E":"2"},"ExpandIterator":{"Iterator":["2"]},"TakeIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthTakeIterable":{"TakeIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"TakeIterator":{"Iterator":["1"]},"SkipIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthSkipIterable":{"SkipIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"SkipIterator":{"Iterator":["1"]},"EmptyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"EmptyIterator":{"Iterator":["1"]},"WhereTypeIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereTypeIterator":{"Iterator":["1"]},"UnmodifiableListBase":{"ListBase":["1"],"UnmodifiableListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ReversedListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"_Record_2_libraries_sources":{"_Record2":[],"_Record":[]},"ConstantMap":{"Map":["1","2"]},"ConstantStringMap":{"ConstantMap":["1","2"],"Map":["1","2"]},"_KeysOrValues":{"Iterable":["1"],"Iterable.E":"1"},"_KeysOrValuesOrElementsIterator":{"Iterator":["1"]},"Instantiation":{"Closure":[],"Function":[]},"Instantiation1":{"Closure":[],"Function":[]},"NullError":{"TypeError":[],"Error":[]},"JsNoSuchMethodError":{"Error":[]},"UnknownJsTypeError":{"Error":[]},"NullThrownFromJavaScriptException":{"Exception":[]},"_StackTrace":{"StackTrace":[]},"Closure":{"Function":[]},"Closure0Args":{"Closure":[],"Function":[]},"Closure2Args":{"Closure":[],"Function":[]},"TearOffClosure":{"Closure":[],"Function":[]},"StaticClosure":{"Closure":[],"Function":[]},"BoundClosure":{"Closure":[],"Function":[]},"RuntimeError":{"Error":[]},"JsLinkedHashMap":{"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"LinkedHashMapKeysIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapKeyIterator":{"Iterator":["1"]},"LinkedHashMapValuesIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapValueIterator":{"Iterator":["1"]},"LinkedHashMapEntriesIterable":{"EfficientLengthIterable":["MapEntry<1,2>"],"Iterable":["MapEntry<1,2>"],"Iterable.E":"MapEntry<1,2>"},"LinkedHashMapEntryIterator":{"Iterator":["MapEntry<1,2>"]},"JsIdentityLinkedHashMap":{"JsLinkedHashMap":["1","2"],"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_Record2":{"_Record":[]},"JSSyntaxRegExp":{"RegExp":[],"Pattern":[]},"_MatchImplementation":{"RegExpMatch":[],"Match":[]},"_AllMatchesIterable":{"Iterable":["RegExpMatch"],"Iterable.E":"RegExpMatch"},"_AllMatchesIterator":{"Iterator":["RegExpMatch"]},"StringMatch":{"Match":[]},"_StringAllMatchesIterable":{"Iterable":["Match"],"Iterable.E":"Match"},"_StringAllMatchesIterator":{"Iterator":["Match"]},"NativeByteBuffer":{"JavaScriptObject":[],"JSObject":[],"ByteBuffer":[],"TrustedGetRuntimeType":[]},"NativeTypedData":{"JavaScriptObject":[],"JSObject":[]},"_UnmodifiableNativeByteBufferView":{"ByteBuffer":[]},"NativeByteData":{"JavaScriptObject":[],"ByteData":[],"JSObject":[],"TrustedGetRuntimeType":[]},"NativeTypedArray":{"JavaScriptIndexingBehavior":["1"],"JavaScriptObject":[],"JSObject":[],"JSIndexable":["1"]},"NativeTypedArrayOfDouble":{"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"]},"NativeTypedArrayOfInt":{"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"]},"NativeFloat32List":{"Float32List":[],"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double","Iterable.E":"double","FixedLengthListMixin.E":"double"},"NativeFloat64List":{"Float64List":[],"ListBase":["double"],"NativeTypedArray":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"JSObject":[],"JSIndexable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double","Iterable.E":"double","FixedLengthListMixin.E":"double"},"NativeInt16List":{"NativeTypedArrayOfInt":[],"Int16List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeInt32List":{"NativeTypedArrayOfInt":[],"Int32List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeInt8List":{"NativeTypedArrayOfInt":[],"Int8List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint16List":{"NativeTypedArrayOfInt":[],"Uint16List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint32List":{"NativeTypedArrayOfInt":[],"Uint32List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint8ClampedList":{"NativeTypedArrayOfInt":[],"Uint8ClampedList":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"NativeUint8List":{"NativeTypedArrayOfInt":[],"Uint8List":[],"ListBase":["int"],"NativeTypedArray":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"JSObject":[],"JSIndexable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int","Iterable.E":"int","FixedLengthListMixin.E":"int"},"_Type":{"Type":[]},"_Error":{"Error":[]},"_TypeError":{"TypeError":[],"Error":[]},"AsyncError":{"Error":[]},"_TimerImpl":{"Timer":[]},"_AsyncAwaitCompleter":{"Completer":["1"]},"_Completer":{"Completer":["1"]},"_AsyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_SyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_Future":{"Future":["1"]},"StreamView":{"Stream":["1"]},"_StreamController":{"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_AsyncStreamController":{"_AsyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_SyncStreamController":{"_SyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"_StreamControllerLifecycle":["1"],"_EventSink":["1"],"_EventDispatch":["1"]},"_ControllerStream":{"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_ControllerSubscription":{"_BufferingStreamSubscription":["1"],"StreamSubscription":["1"],"_EventSink":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamSinkWrapper":{"StreamSink":["1"]},"_BufferingStreamSubscription":{"StreamSubscription":["1"],"_EventSink":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamImpl":{"Stream":["1"]},"_DelayedData":{"_DelayedEvent":["1"]},"_DelayedError":{"_DelayedEvent":["@"]},"_DelayedDone":{"_DelayedEvent":["@"]},"_DoneStreamSubscription":{"StreamSubscription":["1"]},"_EmptyStream":{"Stream":["1"],"Stream.T":"1"},"_ForwardingStream":{"Stream":["2"]},"_ForwardingStreamSubscription":{"_BufferingStreamSubscription":["2"],"StreamSubscription":["2"],"_EventSink":["2"],"_EventDispatch":["2"],"_BufferingStreamSubscription.T":"2"},"_MapStream":{"_ForwardingStream":["1","2"],"Stream":["2"],"Stream.T":"2"},"_ZoneSpecification":{"ZoneSpecification":[]},"_ZoneDelegate":{"ZoneDelegate":[]},"_Zone":{"Zone":[]},"_CustomZone":{"_Zone":[],"Zone":[]},"_RootZone":{"_Zone":[],"Zone":[]},"_SplayTreeSetNode":{"_SplayTreeNode":["1","_SplayTreeSetNode<1>"],"_SplayTreeNode.K":"1","_SplayTreeNode.1":"_SplayTreeSetNode<1>"},"_HashMap":{"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_IdentityHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_CustomHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"HashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashMapKeyIterator":{"Iterator":["1"]},"_LinkedCustomHashMap":{"JsLinkedHashMap":["1","2"],"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashSet":{"_SetBase":["1"],"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashSetIterator":{"Iterator":["1"]},"_LinkedHashSet":{"_SetBase":["1"],"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_LinkedHashSetIterator":{"Iterator":["1"]},"UnmodifiableListView":{"ListBase":["1"],"UnmodifiableListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListBase.E":"1","Iterable.E":"1","UnmodifiableListMixin.E":"1"},"ListBase":{"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"MapBase":{"Map":["1","2"]},"MapView":{"Map":["1","2"]},"UnmodifiableMapView":{"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ListQueue":{"Queue":["1"],"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"_ListQueueIterator":{"Iterator":["1"]},"SetBase":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SetBase":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SplayTreeIterator":{"Iterator":["3"]},"_SplayTreeKeyIterator":{"_SplayTreeIterator":["1","2","1"],"Iterator":["1"],"_SplayTreeIterator.K":"1","_SplayTreeIterator.T":"1","_SplayTreeIterator.1":"2"},"SplayTreeSet":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"_SplayTree":["1","_SplayTreeSetNode<1>"],"Iterable":["1"],"Iterable.E":"1","_SplayTree.1":"_SplayTreeSetNode<1>","_SplayTree.K":"1"},"Encoding":{"Codec":["String","List"]},"_JsonMap":{"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"_JsonMapKeyIterable":{"ListIterable":["String"],"EfficientLengthIterable":["String"],"Iterable":["String"],"ListIterable.E":"String","Iterable.E":"String"},"AsciiCodec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"_UnicodeSubsetEncoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"AsciiEncoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"_UnicodeSubsetDecoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"AsciiDecoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Base64Codec":{"Codec":["List","String"],"Codec.S":"List"},"Base64Encoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Base64Decoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Converter":{"StreamTransformer":["1","2"]},"JsonUnsupportedObjectError":{"Error":[]},"JsonCyclicError":{"Error":[]},"JsonCodec":{"Codec":["Object?","String"],"Codec.S":"Object?"},"JsonEncoder":{"Converter":["Object?","String"],"StreamTransformer":["Object?","String"]},"JsonDecoder":{"Converter":["String","Object?"],"StreamTransformer":["String","Object?"]},"Latin1Codec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"Latin1Encoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Latin1Decoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"Utf8Codec":{"Encoding":[],"Codec":["String","List"],"Codec.S":"String"},"Utf8Encoder":{"Converter":["String","List"],"StreamTransformer":["String","List"]},"Utf8Decoder":{"Converter":["List","String"],"StreamTransformer":["List","String"]},"BigInt":{"Comparable":["BigInt"]},"DateTime":{"Comparable":["DateTime"]},"double":{"num":[],"Comparable":["num"]},"Duration":{"Comparable":["Duration"]},"int":{"num":[],"Comparable":["num"]},"List":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"num":{"Comparable":["num"]},"RegExp":{"Pattern":[]},"RegExpMatch":{"Match":[]},"Set":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"String":{"Comparable":["String"],"Pattern":[]},"_BigIntImpl":{"BigInt":[],"Comparable":["BigInt"]},"AssertionError":{"Error":[]},"TypeError":{"Error":[]},"ArgumentError":{"Error":[]},"RangeError":{"Error":[]},"IndexError":{"Error":[]},"UnsupportedError":{"Error":[]},"UnimplementedError":{"Error":[]},"StateError":{"Error":[]},"ConcurrentModificationError":{"Error":[]},"OutOfMemoryError":{"Error":[]},"StackOverflowError":{"Error":[]},"_Exception":{"Exception":[]},"FormatException":{"Exception":[]},"IntegerDivisionByZeroException":{"Exception":[],"Error":[]},"_StringStackTrace":{"StackTrace":[]},"StringBuffer":{"StringSink":[]},"_Uri":{"Uri":[]},"_SimpleUri":{"Uri":[]},"_DataUri":{"Uri":[]},"NullRejectionException":{"Exception":[]},"DelegatingStreamSink":{"StreamSink":["1"]},"ErrorResult":{"Result":["0&"]},"ValueResult":{"Result":["1"]},"_NextRequest":{"_EventRequest":["1"]},"_HasNextRequest":{"_EventRequest":["1"]},"BuiltList":{"Iterable":["1"]},"_BuiltList":{"BuiltList":["1"],"Iterable":["1"],"Iterable.E":"1"},"_BuiltListMultimap":{"BuiltListMultimap":["1","2"]},"_BuiltMap":{"BuiltMap":["1","2"]},"BuiltSet":{"Iterable":["1"]},"_BuiltSet":{"BuiltSet":["1"],"Iterable":["1"],"Iterable.E":"1"},"_BuiltSetMultimap":{"BuiltSetMultimap":["1","2"]},"BuiltValueNullFieldError":{"Error":[]},"BuiltValueNestedFieldError":{"Error":[]},"BoolJsonObject":{"JsonObject":[]},"ListJsonObject":{"JsonObject":[]},"MapJsonObject":{"JsonObject":[]},"NumJsonObject":{"JsonObject":[]},"StringJsonObject":{"JsonObject":[]},"DeserializationError":{"Error":[]},"BigIntSerializer":{"PrimitiveSerializer":["BigInt"],"Serializer":["BigInt"]},"BoolSerializer":{"PrimitiveSerializer":["bool"],"Serializer":["bool"]},"BuiltJsonSerializers":{"Serializers":[]},"BuiltListMultimapSerializer":{"StructuredSerializer":["BuiltListMultimap<@,@>"],"Serializer":["BuiltListMultimap<@,@>"]},"BuiltListSerializer":{"StructuredSerializer":["BuiltList<@>"],"Serializer":["BuiltList<@>"]},"BuiltMapSerializer":{"StructuredSerializer":["BuiltMap<@,@>"],"Serializer":["BuiltMap<@,@>"]},"BuiltSetMultimapSerializer":{"StructuredSerializer":["BuiltSetMultimap<@,@>"],"Serializer":["BuiltSetMultimap<@,@>"]},"BuiltSetSerializer":{"StructuredSerializer":["BuiltSet<@>"],"Serializer":["BuiltSet<@>"]},"DateTimeSerializer":{"PrimitiveSerializer":["DateTime"],"Serializer":["DateTime"]},"DoubleSerializer":{"PrimitiveSerializer":["double"],"Serializer":["double"]},"DurationSerializer":{"PrimitiveSerializer":["Duration"],"Serializer":["Duration"]},"Int32Serializer":{"PrimitiveSerializer":["Int32"],"Serializer":["Int32"]},"Int64Serializer":{"PrimitiveSerializer":["Int64"],"Serializer":["Int64"]},"IntSerializer":{"PrimitiveSerializer":["int"],"Serializer":["int"]},"JsonObjectSerializer":{"PrimitiveSerializer":["JsonObject"],"Serializer":["JsonObject"]},"NullSerializer":{"PrimitiveSerializer":["Null"],"Serializer":["Null"]},"NumSerializer":{"PrimitiveSerializer":["num"],"Serializer":["num"]},"RegExpSerializer":{"PrimitiveSerializer":["RegExp"],"Serializer":["RegExp"]},"StringSerializer":{"PrimitiveSerializer":["String"],"Serializer":["String"]},"Uint8ListSerializer":{"PrimitiveSerializer":["Uint8List"],"Serializer":["Uint8List"]},"UriSerializer":{"PrimitiveSerializer":["Uri"],"Serializer":["Uri"]},"CanonicalizedMap":{"Map":["2","3"]},"DefaultEquality":{"Equality":["1"]},"IterableEquality":{"Equality":["Iterable<1>"]},"ListEquality":{"Equality":["List<1>"]},"_UnorderedEquality":{"Equality":["2"]},"SetEquality":{"_UnorderedEquality":["1","Set<1>"],"Equality":["Set<1>"],"_UnorderedEquality.E":"1","_UnorderedEquality.T":"Set<1>"},"MapEquality":{"Equality":["Map<1,2>"]},"DeepCollectionEquality":{"Equality":["@"]},"QueueList":{"ListBase":["1"],"List":["1"],"Queue":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListBase.E":"1","QueueList.E":"1","Iterable.E":"1"},"_CastQueueList":{"QueueList":["2"],"ListBase":["2"],"List":["2"],"Queue":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListBase.E":"2","QueueList.E":"2","Iterable.E":"2"},"_$BuildStatusSerializer":{"PrimitiveSerializer":["BuildStatus"],"Serializer":["BuildStatus"]},"_$BuildResultSerializer":{"StructuredSerializer":["BuildResult"],"Serializer":["BuildResult"]},"_$BuildResult":{"BuildResult":[]},"_$ConnectRequestSerializer":{"StructuredSerializer":["ConnectRequest"],"Serializer":["ConnectRequest"]},"_$ConnectRequest":{"ConnectRequest":[]},"_$DebugEventSerializer":{"StructuredSerializer":["DebugEvent"],"Serializer":["DebugEvent"]},"_$BatchedDebugEventsSerializer":{"StructuredSerializer":["BatchedDebugEvents"],"Serializer":["BatchedDebugEvents"]},"_$DebugEvent":{"DebugEvent":[]},"_$BatchedDebugEvents":{"BatchedDebugEvents":[]},"_$DebugInfoSerializer":{"StructuredSerializer":["DebugInfo"],"Serializer":["DebugInfo"]},"_$DebugInfo":{"DebugInfo":[]},"_$DevToolsRequestSerializer":{"StructuredSerializer":["DevToolsRequest"],"Serializer":["DevToolsRequest"]},"_$DevToolsResponseSerializer":{"StructuredSerializer":["DevToolsResponse"],"Serializer":["DevToolsResponse"]},"_$DevToolsRequest":{"DevToolsRequest":[]},"_$DevToolsResponse":{"DevToolsResponse":[]},"_$ErrorResponseSerializer":{"StructuredSerializer":["ErrorResponse"],"Serializer":["ErrorResponse"]},"_$ErrorResponse":{"ErrorResponse":[]},"_$ExtensionRequestSerializer":{"StructuredSerializer":["ExtensionRequest"],"Serializer":["ExtensionRequest"]},"_$ExtensionResponseSerializer":{"StructuredSerializer":["ExtensionResponse"],"Serializer":["ExtensionResponse"]},"_$ExtensionEventSerializer":{"StructuredSerializer":["ExtensionEvent"],"Serializer":["ExtensionEvent"]},"_$BatchedEventsSerializer":{"StructuredSerializer":["BatchedEvents"],"Serializer":["BatchedEvents"]},"_$ExtensionRequest":{"ExtensionRequest":[]},"_$ExtensionResponse":{"ExtensionResponse":[]},"_$ExtensionEvent":{"ExtensionEvent":[]},"_$BatchedEvents":{"BatchedEvents":[]},"_$HotReloadRequestSerializer":{"StructuredSerializer":["HotReloadRequest"],"Serializer":["HotReloadRequest"]},"_$HotReloadRequest":{"HotReloadRequest":[]},"_$HotReloadResponseSerializer":{"StructuredSerializer":["HotReloadResponse"],"Serializer":["HotReloadResponse"]},"_$HotReloadResponse":{"HotReloadResponse":[]},"_$IsolateExitSerializer":{"StructuredSerializer":["IsolateExit"],"Serializer":["IsolateExit"]},"_$IsolateStartSerializer":{"StructuredSerializer":["IsolateStart"],"Serializer":["IsolateStart"]},"_$IsolateExit":{"IsolateExit":[]},"_$IsolateStart":{"IsolateStart":[]},"_$RegisterEventSerializer":{"StructuredSerializer":["RegisterEvent"],"Serializer":["RegisterEvent"]},"_$RegisterEvent":{"RegisterEvent":[]},"_$RunRequestSerializer":{"StructuredSerializer":["RunRequest"],"Serializer":["RunRequest"]},"_$RunRequest":{"RunRequest":[]},"_$ServiceExtensionRequestSerializer":{"StructuredSerializer":["ServiceExtensionRequest"],"Serializer":["ServiceExtensionRequest"]},"_$ServiceExtensionRequest":{"ServiceExtensionRequest":[]},"_$ServiceExtensionResponseSerializer":{"StructuredSerializer":["ServiceExtensionResponse"],"Serializer":["ServiceExtensionResponse"]},"_$ServiceExtensionResponse":{"ServiceExtensionResponse":[]},"SseSocketClient":{"SocketClient":[]},"WebSocketClient":{"SocketClient":[]},"Int32":{"Comparable":["Object"]},"Int64":{"Comparable":["Object"]},"ByteStream":{"StreamView":["List"],"Stream":["List"],"Stream.T":"List","StreamView.T":"List"},"ClientException":{"Exception":[]},"Request":{"BaseRequest":[]},"StreamedResponseV2":{"StreamedResponse":[]},"CaseInsensitiveMap":{"CanonicalizedMap":["String","String","1"],"Map":["String","1"],"CanonicalizedMap.K":"String","CanonicalizedMap.V":"1","CanonicalizedMap.C":"String"},"Level":{"Comparable":["Level"]},"PathException":{"Exception":[]},"PosixStyle":{"InternalStyle":[]},"UrlStyle":{"InternalStyle":[]},"WindowsStyle":{"InternalStyle":[]},"FileLocation":{"SourceLocation":[],"Comparable":["SourceLocation"]},"_FileSpan":{"SourceSpanWithContext":[],"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceLocation":{"Comparable":["SourceLocation"]},"SourceLocationMixin":{"SourceLocation":[],"Comparable":["SourceLocation"]},"SourceSpan":{"Comparable":["SourceSpan"]},"SourceSpanBase":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceSpanException":{"Exception":[]},"SourceSpanFormatException":{"FormatException":[],"Exception":[]},"SourceSpanMixin":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SourceSpanWithContext":{"SourceSpan":[],"Comparable":["SourceSpan"]},"SseClient":{"StreamChannel":["String?"]},"GuaranteeChannel":{"StreamChannel":["1"]},"_GuaranteeSink":{"StreamSink":["1"]},"StreamChannelMixin":{"StreamChannel":["1"]},"StringScannerException":{"FormatException":[],"Exception":[]},"_EventStream":{"Stream":["1"],"Stream.T":"1"},"_EventStreamSubscription":{"StreamSubscription":["1"]},"BrowserWebSocket":{"WebSocket":[]},"TextDataReceived":{"WebSocketEvent":[]},"BinaryDataReceived":{"WebSocketEvent":[]},"CloseReceived":{"WebSocketEvent":[]},"WebSocketException":{"Exception":[]},"WebSocketConnectionClosed":{"Exception":[]},"AdapterWebSocketChannel":{"WebSocketChannel":[],"StreamChannel":["@"]},"_WebSocketSink":{"WebSocketSink":[],"DelegatingStreamSink":["@"],"StreamSink":["@"],"DelegatingStreamSink.T":"@"},"WebSocketChannelException":{"Exception":[]},"DdcLibraryBundleRestarter":{"Restarter":[]},"DdcRestarter":{"Restarter":[]},"RequireRestarter":{"Restarter":[]},"HotReloadFailedException":{"Exception":[]},"Int8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8ClampedList":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Float32List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]},"Float64List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]}}')); A._Universe_addErasedTypes(init.typeUniverse, JSON.parse('{"UnmodifiableListBase":1,"__CastListBase__CastIterableBase_ListMixin":2,"NativeTypedArray":1,"_DelayedEvent":1,"_SplayTreeSet__SplayTree_Iterable":1,"_SplayTreeSet__SplayTree_Iterable_SetMixin":1,"_QueueList_Object_ListMixin":1,"StreamChannelMixin":1}')); var string$ = { x00_____: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\u03f6\x00\u0404\u03f4 \u03f4\u03f6\u01f6\u01f6\u03f6\u03fc\u01f4\u03ff\u03ff\u0584\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u05d4\u01f4\x00\u01f4\x00\u0504\u05c4\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u0400\x00\u0400\u0200\u03f7\u0200\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u0200\u0200\u0200\u03f7\x00", @@ -28250,6 +28794,7 @@ MapEntry_of_Object_and_List__Highlight: findType("MapEntry>"), MapEquality_dynamic_dynamic: findType("MapEquality<@,@>"), Map_String_String: findType("Map"), + Map_String_dynamic: findType("Map"), Map_dynamic_dynamic: findType("Map<@,@>"), Map_of_String_and_nullable_Object: findType("Map"), MappedListIterable_String_dynamic: findType("MappedListIterable"), @@ -28273,6 +28818,8 @@ RunRequest: findType("RunRequest"), SerializerPlugin: findType("SerializerPlugin"), Serializer_dynamic: findType("Serializer<@>"), + ServiceExtensionRequest: findType("ServiceExtensionRequest"), + ServiceExtensionResponse: findType("ServiceExtensionResponse"), SetBuilder_dynamic: findType("SetBuilder<@>"), SetEquality_dynamic: findType("SetEquality<@>"), SetMultimapBuilder_dynamic_dynamic: findType("SetMultimapBuilder<@,@>"), @@ -28349,6 +28896,7 @@ nullable_ListBuilder_DebugEvent: findType("ListBuilder?"), nullable_ListBuilder_ExtensionEvent: findType("ListBuilder?"), nullable_List_dynamic: findType("List<@>?"), + nullable_Map_String_dynamic: findType("Map?"), nullable_Map_of_nullable_Object_and_nullable_Object: findType("Map?"), nullable_Object: findType("Object?"), nullable_Result_DebugEvent: findType("Result?"), @@ -28375,6 +28923,7 @@ nullable_void_Function_HotReloadResponseBuilder: findType("~(HotReloadResponseBuilder)?"), nullable_void_Function_JSObject: findType("~(JSObject)?"), nullable_void_Function_RegisterEventBuilder: findType("~(RegisterEventBuilder)?"), + nullable_void_Function_ServiceExtensionResponseBuilder: findType("~(ServiceExtensionResponseBuilder)?"), num: findType("num"), void: findType("~"), void_Function: findType("~()"), @@ -28595,12 +29144,18 @@ B.Type_ExtensionRequest_9GR = A.typeLiteral("ExtensionRequest"); B.Type__$ExtensionRequest_o1C = A.typeLiteral("_$ExtensionRequest"); B.List_2dD = A._setArrayType(makeConstList([B.Type_ExtensionRequest_9GR, B.Type__$ExtensionRequest_o1C]), type$.JSArray_Type); + B.Type_ServiceExtensionRequest_K8g = A.typeLiteral("ServiceExtensionRequest"); + B.Type__$ServiceExtensionRequest_n9i = A.typeLiteral("_$ServiceExtensionRequest"); + B.List_4i4 = A._setArrayType(makeConstList([B.Type_ServiceExtensionRequest_K8g, B.Type__$ServiceExtensionRequest_n9i]), type$.JSArray_Type); B.Type_DebugInfo_ua9 = A.typeLiteral("DebugInfo"); B.Type__$DebugInfo_ywz = A.typeLiteral("_$DebugInfo"); B.List_55I = A._setArrayType(makeConstList([B.Type_DebugInfo_ua9, B.Type__$DebugInfo_ywz]), type$.JSArray_Type); B.Type_ErrorResponse_WMn = A.typeLiteral("ErrorResponse"); B.Type__$ErrorResponse_9Ps = A.typeLiteral("_$ErrorResponse"); B.List_5LV = A._setArrayType(makeConstList([B.Type_ErrorResponse_WMn, B.Type__$ErrorResponse_9Ps]), type$.JSArray_Type); + B.Type_ServiceExtensionResponse_84R = A.typeLiteral("ServiceExtensionResponse"); + B.Type__$ServiceExtensionResponse_UiR = A.typeLiteral("_$ServiceExtensionResponse"); + B.List_5rA = A._setArrayType(makeConstList([B.Type_ServiceExtensionResponse_84R, B.Type__$ServiceExtensionResponse_UiR]), type$.JSArray_Type); B.Type_HotReloadResponse_Gqc = A.typeLiteral("HotReloadResponse"); B.Type__$HotReloadResponse_56g = A.typeLiteral("_$HotReloadResponse"); B.List_DqJ = A._setArrayType(makeConstList([B.Type_HotReloadResponse_Gqc, B.Type__$HotReloadResponse_56g]), type$.JSArray_Type); @@ -28857,10 +29412,14 @@ t1.add$1(0, $.$get$_$isolateStartSerializer()); t1.add$1(0, $.$get$_$registerEventSerializer()); t1.add$1(0, $.$get$_$runRequestSerializer()); + t1.add$1(0, $.$get$_$serviceExtensionRequestSerializer()); + t1.add$1(0, $.$get$_$serviceExtensionResponseSerializer()); t1.addBuilderFactory$2(B.FullType_3Xm, new A._$serializers_closure()); t1.addBuilderFactory$2(B.FullType_ahP, new A._$serializers_closure0()); return t1.build$0(); }); + _lazy($, "_$serviceExtensionRequestSerializer", "$get$_$serviceExtensionRequestSerializer", () => new A._$ServiceExtensionRequestSerializer()); + _lazy($, "_$serviceExtensionResponseSerializer", "$get$_$serviceExtensionResponseSerializer", () => new A._$ServiceExtensionResponseSerializer()); _lazyFinal($, "_logger", "$get$_logger", () => A.Logger_Logger("Utilities")); _lazyFinal($, "BaseRequest__tokenRE", "$get$BaseRequest__tokenRE", () => A.RegExp_RegExp("^[\\w!#%&'*+\\-.^`|~]+$", true, false)); _lazyFinal($, "_digitRegex", "$get$_digitRegex", () => A.RegExp_RegExp("^\\d+$", true, false)); diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index fb9d18667..3b5440164 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -8,33 +8,62 @@ import 'package:dwds/src/services/chrome_proxy_service.dart' show ChromeProxyService; import 'package:dwds/src/services/debug_service.dart'; -/// A container for all the services required for debugging an application. -class AppDebugServices { - final DebugService debugService; - final DwdsVmClient dwdsVmClient; - final DwdsStats dwdsStats; - final Uri? ddsUri; - - ChromeProxyService get chromeProxyService => - debugService.chromeProxyService as ChromeProxyService; +/// Common interface for debug service containers. +abstract class IAppDebugServices { + dynamic get debugService; + dynamic get dwdsVmClient; + dynamic get dwdsStats; + Uri? get ddsUri; + String? get connectedInstanceId; + set connectedInstanceId(String? id); + Future close(); + dynamic get chromeProxyService; + dynamic get webSocketProxyService; +} - /// Null until [close] is called. - /// - /// All subsequent calls to [close] will return this future. +/// Chrome-based debug services container. +class AppDebugServices implements IAppDebugServices { + final DebugService _debugService; + final DwdsVmClient _dwdsVmClient; + final DwdsStats _dwdsStats; + final Uri? _ddsUri; Future? _closed; - - /// The instance ID for the currently connected application, if there is one. - /// - /// We only allow a given app to be debugged in a single tab at a time. - String? connectedInstanceId; + String? _connectedInstanceId; AppDebugServices( - this.debugService, - this.dwdsVmClient, - this.dwdsStats, - this.ddsUri, + this._debugService, + this._dwdsVmClient, + this._dwdsStats, + this._ddsUri, ); + @override + DebugService get debugService => _debugService; + + @override + DwdsVmClient get dwdsVmClient => _dwdsVmClient; + + @override + DwdsStats get dwdsStats => _dwdsStats; + + @override + Uri? get ddsUri => _ddsUri; + + @override + String? get connectedInstanceId => _connectedInstanceId; + + @override + set connectedInstanceId(String? id) => _connectedInstanceId = id; + + @override + ChromeProxyService get chromeProxyService => + debugService.chromeProxyService as ChromeProxyService; + + // WebSocket functionality not available in Chrome-based service + @override + dynamic get webSocketProxyService => null; + + @override Future close() => _closed ??= Future.wait([debugService.close(), dwdsVmClient.close()]); } diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 3141d2c57..045c3e9bf 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -7,8 +7,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:dwds/data/debug_event.dart'; -import 'package:dwds/data/hot_reload_request.dart'; -import 'package:dwds/data/hot_reload_response.dart'; import 'package:dwds/data/register_event.dart'; import 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/connections/app_connection.dart'; @@ -34,13 +32,8 @@ import 'package:vm_service/vm_service.dart' hide vmServiceVersion; import 'package:vm_service_interface/vm_service_interface.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; -/// Defines callbacks for sending messages to the connected client application. -typedef SendClientRequest = void Function(Object request); - /// A proxy from the chrome debug protocol to the dart vm service protocol. class ChromeProxyService implements VmServiceInterface { - final bool useWebSocket; - /// Cache of all existing StreamControllers. /// /// These are all created through [onEvent]. @@ -134,12 +127,6 @@ class ChromeProxyService implements VmServiceInterface { bool terminatingIsolates = false; - /// Callback function to send messages to the connected client application. - final SendClientRequest sendClientRequest; - - /// Pending hot reload request waiting for a response from the client. - Completer? _pendingHotReload; - ChromeProxyService._( this._vm, this.root, @@ -150,9 +137,7 @@ class ChromeProxyService implements VmServiceInterface { this._skipLists, this.executionContext, this._compiler, - this.sendClientRequest, { - this.useWebSocket = false, - }) { + ) { final debugger = Debugger.create( remoteDebugger, _streamNotify, @@ -170,9 +155,7 @@ class ChromeProxyService implements VmServiceInterface { AppConnection appConnection, ExecutionContext executionContext, ExpressionCompiler? expressionCompiler, - SendClientRequest sendClientRequest, { - bool useWebSocket = false, - }) async { + ) async { final vm = VM( name: 'ChromeDebugProxy', operatingSystem: Platform.operatingSystem, @@ -201,32 +184,11 @@ class ChromeProxyService implements VmServiceInterface { skipLists, executionContext, expressionCompiler, - sendClientRequest, - useWebSocket: useWebSocket, ); safeUnawaited(service.createIsolate(appConnection, newConnection: true)); return service; } - /// Completes the hot reload completer associated with the response ID. - void completeHotReload(HotReloadResponse response) { - final completer = _pendingHotReload; - _pendingHotReload = null; - if (completer != null) { - if (response.success) { - completer.complete(response); - } else { - completer.completeError( - response.errorMessage ?? 'Unknown client error during hot reload', - ); - } - } else { - _logger.warning( - 'Received hot reload response but no pending completer was found (id: ${response.id})', - ); - } - } - /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. void _initializeEntrypoint(String entrypoint) { _locations.initialize(entrypoint); @@ -1169,11 +1131,7 @@ class ChromeProxyService implements VmServiceInterface { ], }; try { - if (useWebSocket) { - await _performWebSocketHotReload(); - } else { - await _performClientSideHotReload(); - } + await _performClientSideHotReload(); } catch (e) { _logger.info('Hot reload failed: $e'); return getFailedReloadReport(e.toString()); @@ -1204,37 +1162,6 @@ class ChromeProxyService implements VmServiceInterface { _logger.info('\$dartHotReloadDwds request complete.'); } - /// Performs a WebSocket-based hot reload by sending a request and waiting for a response. - /// If [requestId] is provided, it will be used for the request; otherwise, a new one is generated. - Future _performWebSocketHotReload({String? requestId}) async { - final id = requestId ?? createId(); - if (_pendingHotReload != null) { - throw StateError('A hot reload is already pending.'); - } - final completer = Completer(); - _pendingHotReload = completer; - const timeout = Duration(seconds: 10); - - _logger.info('Issuing HotReloadRequest with ID ($id) to client.'); - sendClientRequest(HotReloadRequest((b) => b.id = id)); - - final response = await completer.future.timeout( - timeout, - onTimeout: - () => - throw TimeoutException( - 'Client did not respond to hot reload request', - timeout, - ), - ); - - if (!response.success) { - throw Exception( - response.errorMessage ?? 'Client reported hot reload failure.', - ); - } - } - @override Future removeBreakpoint(String isolateId, String breakpointId) => wrapInErrorHandlerAsync( diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index ac90a0e17..34202ad09 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -231,7 +231,6 @@ class DebugService { int? ddsPort, bool useSse = false, ExpressionCompiler? expressionCompiler, - required SendClientRequest sendClientRequest, }) async { final root = assetReader.basePath; final chromeProxyService = await ChromeProxyService.create( @@ -241,7 +240,6 @@ class DebugService { appConnection, executionContext, expressionCompiler, - sendClientRequest, ); final authToken = _makeAuthToken(); final serviceExtensionRegistry = ServiceExtensionRegistry(); diff --git a/dwds/lib/src/services/web_socket_app_debug_services.dart b/dwds/lib/src/services/web_socket_app_debug_services.dart new file mode 100644 index 000000000..4d8539238 --- /dev/null +++ b/dwds/lib/src/services/web_socket_app_debug_services.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2025, 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. + +import 'dart:async'; +import 'package:dwds/src/services/app_debug_services.dart'; +import 'package:dwds/src/services/web_socket_debug_service.dart'; +import 'package:dwds/src/web_socket_dwds_vm_client.dart'; + +/// WebSocket-based implementation of app debug services. +/// +/// Provides debugging capabilities without Chrome dependencies. +class WebSocketAppDebugServices implements IAppDebugServices { + final WebSocketDebugService _debugService; + final WebSocketDwdsVmClient _dwdsVmClient; + Future? _closed; + String? _connectedInstanceId; + + WebSocketAppDebugServices(this._debugService, this._dwdsVmClient); + + @override + WebSocketDebugService get debugService => _debugService; + + @override + WebSocketDwdsVmClient get dwdsVmClient => _dwdsVmClient; + + @override + String? get connectedInstanceId => _connectedInstanceId; + + @override + set connectedInstanceId(String? id) => _connectedInstanceId = id; + + // WebSocket-only service - Chrome/DDS features not available + @override + dynamic get dwdsStats => null; + @override + Uri? get ddsUri => null; + @override + dynamic get chromeProxyService => + throw UnsupportedError( + 'Chrome proxy service not available in WebSocket-only mode', + ); + + @override + dynamic get webSocketProxyService => _debugService.webSocketProxyService; + @override + Future close() { + return _closed ??= Future.wait([ + debugService.close(), + dwdsVmClient.close(), + ]); + } +} diff --git a/dwds/lib/src/services/web_socket_debug_service.dart b/dwds/lib/src/services/web_socket_debug_service.dart new file mode 100644 index 000000000..3a3b403ab --- /dev/null +++ b/dwds/lib/src/services/web_socket_debug_service.dart @@ -0,0 +1,178 @@ +// Copyright (c) 2025, 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:dds/dds_launcher.dart'; +import 'package:dwds/src/connections/app_connection.dart'; + +import 'package:dwds/src/services/web_socket_proxy_service.dart'; +import 'package:dwds/src/utilities/server.dart'; +import 'package:logging/logging.dart'; +import 'package:shelf_web_socket/shelf_web_socket.dart'; +import 'package:vm_service_interface/vm_service_interface.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +/// Defines callbacks for sending messages to the connected client. +typedef SendClientRequest = void Function(Object request); + +// Connection control for WebSocket clients +bool _acceptNewConnections = true; +int _clientsConnected = 0; + +/// WebSocket-based debug service for web debugging. +/// +/// Provides hot reload and service extension support without Chrome dependencies. +class WebSocketDebugService { + final String hostname; + final int port; + final String authToken; + final HttpServer _server; + final WebSocketProxyService _webSocketProxyService; + final ServiceExtensionRegistry _serviceExtensionRegistry; + + Future? _closed; + DartDevelopmentServiceLauncher? _dds; + + WebSocketDebugService._( + this.hostname, + this.port, + this.authToken, + this._webSocketProxyService, + this._serviceExtensionRegistry, + this._server, + ); + + /// Returns the WebSocketProxyService instance. + WebSocketProxyService get webSocketProxyService => _webSocketProxyService; + + /// Returns the ServiceExtensionRegistry instance. + ServiceExtensionRegistry get serviceExtensionRegistry => + _serviceExtensionRegistry; + + /// Closes the debug service and associated resources. + Future close() => + _closed ??= Future.wait([ + _server.close(), + if (_dds != null) _dds!.shutdown(), + ]); + + /// Starts DDS (Dart Development Service). + Future startDartDevelopmentService({ + int? ddsPort, + }) async { + const timeout = Duration(seconds: 10); + + try { + _dds = await DartDevelopmentServiceLauncher.start( + remoteVmServiceUri: Uri( + scheme: 'http', + host: hostname, + port: port, + path: authToken, + ), + serviceUri: Uri(scheme: 'http', host: hostname, port: ddsPort ?? 0), + ).timeout(timeout); + } catch (e) { + throw Exception('Failed to start DDS: $e'); + } + return _dds!; + } + + String get uri => + Uri(scheme: 'ws', host: hostname, port: port, path: authToken).toString(); + + static Future start( + String hostname, + AppConnection appConnection, { + required SendClientRequest sendClientRequest, + }) async { + final authToken = _makeAuthToken(); + final serviceExtensionRegistry = ServiceExtensionRegistry(); + + final webSocketProxyService = await WebSocketProxyService.create( + sendClientRequest, + appConnection, + ); + + final handler = _createWebSocketHandler( + serviceExtensionRegistry, + webSocketProxyService, + ); + + final server = await startHttpServer(hostname, port: 0); + serveHttpRequests(server, handler, (e, s) { + Logger('WebSocketDebugService').warning('Error serving requests', e); + }); + + return WebSocketDebugService._( + server.address.host, + server.port, + authToken, + webSocketProxyService, + serviceExtensionRegistry, + server, + ); + } + + /// Creates the WebSocket handler for incoming connections. + static dynamic _createWebSocketHandler( + ServiceExtensionRegistry serviceExtensionRegistry, + WebSocketProxyService webSocketProxyService, + ) { + return webSocketHandler((WebSocketChannel webSocket) { + if (!_acceptNewConnections) { + webSocket.sink.add( + jsonEncode({ + 'error': 'Cannot connect: another service has taken control.', + }), + ); + webSocket.sink.close(); + return; + } + + final responseController = StreamController>(); + webSocket.sink.addStream(responseController.stream.map(jsonEncode)); + + final inputStream = webSocket.stream.map((value) { + if (value is List) { + value = utf8.decode(value); + } else if (value is! String) { + throw StateError( + 'Unexpected value type from web socket: ${value.runtimeType}', + ); + } + return Map.from(jsonDecode(value)); + }); + + ++_clientsConnected; + VmServerConnection( + inputStream, + responseController.sink, + serviceExtensionRegistry, + webSocketProxyService, + ).done.whenComplete(() { + --_clientsConnected; + if (!_acceptNewConnections && _clientsConnected == 0) { + _acceptNewConnections = true; + } + }); + }); + } +} + +// Creates a random auth token for more secure connections. +String _makeAuthToken() { + final tokenBytes = 8; + final bytes = Uint8List(tokenBytes); + final random = Random.secure(); + for (var i = 0; i < tokenBytes; i++) { + bytes[i] = random.nextInt(256); + } + return base64Url.encode(bytes); +} diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart new file mode 100644 index 000000000..86974ba48 --- /dev/null +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -0,0 +1,668 @@ +// Copyright (c) 2025, 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. + +import 'dart:async'; + +import 'package:dwds/data/hot_reload_request.dart'; +import 'package:dwds/data/hot_reload_response.dart'; +import 'package:dwds/data/service_extension_request.dart'; +import 'package:dwds/data/service_extension_response.dart'; +import 'package:dwds/src/connections/app_connection.dart'; +import 'package:dwds/src/events.dart'; +import 'package:dwds/src/utilities/dart_uri.dart'; +import 'package:dwds/src/utilities/shared.dart'; +import 'package:logging/logging.dart'; +// Ensure RPCError and RPCErrorKind are available for error handling +import 'package:pub_semver/pub_semver.dart' as semver; +import 'package:vm_service/vm_service.dart' as vm_service; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service_interface/vm_service_interface.dart'; + +/// Defines callbacks for sending messages to the connected client. +typedef SendClientRequest = void Function(Object request); + +const _pauseIsolatesOnStartFlag = 'pause_isolates_on_start'; + +/// WebSocket-based VM service proxy for web debugging. +/// +/// Provides hot reload and service extension support via WebSocket communication. +class WebSocketProxyService implements VmServiceInterface { + final _logger = Logger('WebSocketProxyService'); + + /// Signals when the isolate is ready. + Future get isInitialized => _initializedCompleter.future; + Completer _initializedCompleter = Completer(); + + /// Active service extension requests by ID. + final Map> + _pendingServiceExtensions = {}; + + /// Sends messages to the client. + final SendClientRequest sendClientRequest; + + /// App connection for this service. + final AppConnection appConnection; + + /// Current hot reload request (one at a time). + Completer? _pendingHotReload; + + /// App connection cleanup subscription. + StreamSubscription? _appConnectionDoneSubscription; + + /// Event stream controllers. + final Map> _streamControllers = {}; + + /// VM service runtime flags. + final Map _currentVmServiceFlags = { + _pauseIsolatesOnStartFlag: false, + }; + + /// Root VM instance. + final vm_service.VM _vm; + + WebSocketProxyService._( + this.sendClientRequest, + this._vm, + this.appConnection, + ); // Isolate state + vm_service.IsolateRef? _isolateRef; + bool _isolateRunning = false; + vm_service.Event? _currentPauseEvent; + bool _hasResumed = false; + + bool get _isIsolateRunning => _isolateRunning; + + /// Creates a new isolate for WebSocket debugging. + /// + /// Destroys existing isolate first if present. Call [destroyIsolate] on restart. + Future createIsolate([AppConnection? appConnectionOverride]) async { + final appConn = appConnectionOverride ?? appConnection; + + // Clean up existing isolate + if (_isIsolateRunning) { + destroyIsolate(); + await Future.delayed(Duration(milliseconds: 10)); + } + + // Auto-cleanup on connection close + await _appConnectionDoneSubscription?.cancel(); + _appConnectionDoneSubscription = appConn.onDone.asStream().listen((_) { + destroyIsolate(); + }); + + // Create isolate reference + final isolateRef = vm_service.IsolateRef( + id: '1', + name: 'main()', + number: '1', + isSystemIsolate: false, + ); + + _isolateRef = isolateRef; + _isolateRunning = true; + _hasResumed = false; + _vm.isolates?.add(isolateRef); + final timestamp = DateTime.now().millisecondsSinceEpoch; + + // Send lifecycle events + _streamNotify( + vm_service.EventStreams.kIsolate, + vm_service.Event( + kind: vm_service.EventKind.kIsolateStart, + timestamp: timestamp, + isolate: isolateRef, + ), + ); + _streamNotify( + vm_service.EventStreams.kIsolate, + vm_service.Event( + kind: vm_service.EventKind.kIsolateRunnable, + timestamp: timestamp, + isolate: isolateRef, + ), + ); + + if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); + + // Send pause event if enabled + if (pauseIsolatesOnStart) { + print('JY - isolate paused on start - sending pause event'); + final pauseEvent = vm_service.Event( + kind: vm_service.EventKind.kPauseStart, + timestamp: timestamp, + isolate: isolateRef, + ); + _currentPauseEvent = pauseEvent; + _hasResumed = false; + _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); + // Flutter tools will call resume() to start the app + + // Auto-resume after a short delay if no debugger is connected + // This handles the case where the app is run from terminal without debug extension + _scheduleAutoResumeIfNeeded(); + } else { + print('JY - isolate not paused on start - sending resume event'); + // If we're not pausing on start, immediately send a resume event + // to ensure the app knows it can start running + _logger.info('Not pausing on start, sending immediate resume event'); + final resumeEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: timestamp, + isolate: isolateRef, + ); + _hasResumed = true; + _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + } + } + + /// Destroys the isolate and cleans up state. + void destroyIsolate() { + _logger.fine('Destroying isolate'); + if (!_isIsolateRunning) return; + + final isolateRef = _isolateRef; + + _appConnectionDoneSubscription?.cancel(); + _appConnectionDoneSubscription = null; + + // Send exit event + if (isolateRef != null) { + _streamNotify( + vm_service.EventStreams.kIsolate, + vm_service.Event( + kind: vm_service.EventKind.kIsolateExit, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: isolateRef, + ), + ); + } + + _vm.isolates?.removeWhere((ref) => ref.id == isolateRef?.id); + + // Reset state + _isolateRef = null; + _isolateRunning = false; + _currentPauseEvent = null; + _hasResumed = false; + + if (_initializedCompleter.isCompleted) { + _initializedCompleter = Completer(); + } + } + + /// Sends events to stream controllers. + void _streamNotify(String streamId, vm_service.Event event) { + final controller = _streamControllers[streamId]; + if (controller == null) return; + controller.add(event); + } + + @override + Future setLibraryDebuggable( + String isolateId, + String libraryId, + bool isDebuggable, + ) { + return _rpcNotSupportedFuture('setLibraryDebuggable'); + } + + @override + Future setIsolatePauseMode( + String isolateId, { + String? exceptionPauseMode, + bool? shouldPauseOnExit, + }) async { + // Not supported in WebSocket mode - return success for compatibility + return Success(); + } + + static Future _rpcNotSupportedFuture(String method) { + return Future.error(_rpcNotSupported(method)); + } + + static RPCError _rpcNotSupported(String method) { + return RPCError( + method, + RPCErrorKind.kMethodNotFound.code, + '$method: Not supported on web devices', + ); + } + + @override + Future getIsolate(String isolateId) => + wrapInErrorHandlerAsync('getIsolate', () => _getIsolate(isolateId)); + + Future _getIsolate(String isolateId) async { + if (!_isIsolateRunning || _isolateRef == null) { + throw vm_service.RPCError( + 'getIsolate', + vm_service.RPCErrorKind.kInvalidParams.code, + 'No running isolate found for id: $isolateId', + ); + } + if (_isolateRef!.id != isolateId) { + throw vm_service.RPCError( + 'getIsolate', + vm_service.RPCErrorKind.kInvalidParams.code, + 'Isolate with id $isolateId not found.', + ); + } + + return vm_service.Isolate( + id: _isolateRef!.id!, + name: _isolateRef!.name, + number: _isolateRef!.number, + startTime: DateTime.now().millisecondsSinceEpoch, + isSystemIsolate: _isolateRef!.isSystemIsolate, + runnable: true, + pauseEvent: _currentPauseEvent, + ); + } + + /// Returns a broadcast stream for the given streamId, creating if needed. + @override + Stream onEvent(String streamId) { + return _streamControllers.putIfAbsent(streamId, () { + switch (streamId) { + case vm_service.EventStreams.kExtension: + case vm_service.EventStreams.kIsolate: + case vm_service.EventStreams.kVM: + case vm_service.EventStreams.kGC: + case vm_service.EventStreams.kTimeline: + case vm_service.EventStreams.kService: + case vm_service.EventStreams.kDebug: + case vm_service.EventStreams.kLogging: + case vm_service.EventStreams.kStdout: + case vm_service.EventStreams.kStderr: + return StreamController.broadcast(); + default: + _logger.warning('Unsupported stream: $streamId'); + throw vm_service.RPCError( + 'streamListen', + vm_service.RPCErrorKind.kInvalidParams.code, + 'Stream `$streamId` not supported on web devices', + ); + } + }).stream; + } + + @override + Future streamListen(String streamId) => + wrapInErrorHandlerAsync('streamListen', () => _streamListen(streamId)); + + Future _streamListen(String streamId) async { + onEvent(streamId); + return vm_service.Success(); + } + + /// Adds events to stream controllers. + void addEvent(String streamId, vm_service.Event event) { + final controller = _streamControllers[streamId]; + if (controller != null && !controller.isClosed) { + controller.add(event); + _logger.fine('Added event to stream $streamId: $event'); + } else { + _logger.warning('Cannot add event to closed/missing stream: $streamId'); + } + } + + static Future create( + SendClientRequest sendClientRequest, + AppConnection appConnection, + ) async { + final vm = vm_service.VM( + name: 'WebSocketDebugProxy', + operatingSystem: 'web', + startTime: DateTime.now().millisecondsSinceEpoch, + version: 'unknown', + isolates: [], + isolateGroups: [], + systemIsolates: [], + systemIsolateGroups: [], + targetCPU: 'Web', + hostCPU: 'DWDS', + architectureBits: -1, + pid: -1, + ); + final service = WebSocketProxyService._( + sendClientRequest, + vm, + appConnection, + ); + safeUnawaited(service.createIsolate(appConnection)); + return service; + } + + /// Returns the root VM object. + @override + Future getVM() => wrapInErrorHandlerAsync('getVM', _getVM); + + Future _getVM() { + // On web, we do not currently support isolates, so isInitialized is not required. + return captureElapsedTime(() async { + return _vm; + }, (result) => DwdsEvent.getVM()); + } + + /// Throws error if remoteDebugger is accessed (not available in WebSocket mode). + dynamic get remoteDebugger { + throw UnsupportedError( + 'remoteDebugger not available in WebSocketProxyService.\n' + 'Called from:\n${StackTrace.current}', + ); + } + + /// Returns supported VM service protocols. + @override + Future getSupportedProtocols() async { + final version = semver.Version.parse(vm_service.vmServiceVersion); + return vm_service.ProtocolList( + protocols: [ + vm_service.Protocol( + protocolName: 'VM Service', + major: version.major, + minor: version.minor, + ), + ], + ); + } + + @override + Future reloadSources( + String isolateId, { + bool? force, + bool? pause, + String? rootLibUri, + String? packagesUri, + }) async { + _logger.info('Attempting a hot reload'); + try { + await _performWebSocketHotReload(); + _logger.info('Hot reload completed successfully'); + return _ReloadReportWithMetadata(success: true); + } catch (e) { + _logger.warning('Hot reload failed: $e'); + return _ReloadReportWithMetadata(success: false, notices: [e.toString()]); + } + } + + /// Completes hot reload with response. + void completeHotReload(HotReloadResponse response) { + final completer = _pendingHotReload; + _pendingHotReload = null; + + if (completer != null) { + if (response.success) { + completer.complete(response); + } else { + completer.completeError( + response.errorMessage ?? 'Unknown client error during hot reload', + ); + } + } else { + _logger.warning( + 'Received hot reload response but no pending completer found (id: ${response.id})', + ); + } + } + + /// Performs WebSocket-based hot reload. + Future _performWebSocketHotReload({String? requestId}) async { + if (_pendingHotReload != null) { + throw StateError('Hot reload already pending'); + } + + final id = requestId ?? createId(); + final completer = Completer(); + _pendingHotReload = completer; + + const timeout = Duration(seconds: 10); + _logger.info('Sending HotReloadRequest with ID ($id) to client'); + + await Future.microtask(() { + sendClientRequest(HotReloadRequest((b) => b.id = id)); + }); + + try { + final response = await completer.future.timeout( + timeout, + onTimeout: () { + _pendingHotReload = null; + throw TimeoutException( + 'Client did not respond to hot reload', + timeout, + ); + }, + ); + + if (!response.success) { + throw Exception(response.errorMessage ?? 'Client reported failure'); + } + } catch (e) { + _pendingHotReload = null; + rethrow; + } + } + + @override + Future callServiceExtension( + String method, { + String? isolateId, + Map? args, + }) => wrapInErrorHandlerAsync( + 'callServiceExtension', + () => _callServiceExtension(method, isolateId: isolateId, args: args), + ); + + /// Calls a service extension on the client. + Future _callServiceExtension( + String method, { + String? isolateId, + Map? args, + }) async { + final requestId = createId(); + if (_pendingServiceExtensions.containsKey(requestId)) { + throw StateError('Service extension call already pending for this ID'); + } + + final completer = Completer(); + _pendingServiceExtensions[requestId] = completer; + + final request = ServiceExtensionRequest.fromArgs( + id: requestId, + method: method, + args: + args != null ? Map.from(args) : {}, + ); + sendClientRequest(request); + + final response = await completer.future.timeout(Duration(seconds: 10)); + _pendingServiceExtensions.remove(requestId); + + if (response.errorMessage != null) { + throw RPCError( + method, + response.errorCode ?? RPCErrorKind.kServerError.code, + response.errorMessage!, + ); + } + return Response()..json = response.result; + } + + /// Completes service extension with response. + void completeServiceExtension(ServiceExtensionResponse response) { + final id = response.id; + final completer = _pendingServiceExtensions.remove(id); + + if (completer != null) { + if (response.success == true) { + completer.complete(response); + } else { + completer.completeError( + response.errorMessage ?? + 'Unknown client error during service extension', + ); + } + } else { + _logger.warning( + 'No pending completer found for service extension (id: $id)', + ); + } + } + + @override + Future setFlag(String name, String value) => + wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value)); + + Future _setFlag(String name, String value) async { + if (!_currentVmServiceFlags.containsKey(name)) { + return _rpcNotSupportedFuture('setFlag'); + } + + assert(value == 'true' || value == 'false'); + final oldValue = _currentVmServiceFlags[name]; + _currentVmServiceFlags[name] = value == 'true'; + + // Handle pause_isolates_on_start flag + if (name == _pauseIsolatesOnStartFlag && + value == 'true' && + oldValue == false) { + // Send pause event for existing isolate if it wasn't paused initially + if (_isIsolateRunning && + _isolateRef != null && + _currentPauseEvent == null) { + final pauseEvent = vm_service.Event( + kind: vm_service.EventKind.kPauseStart, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ); + _currentPauseEvent = pauseEvent; + _hasResumed = false; + _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); + } + } + + return Success(); + } + + @override + Future lookupResolvedPackageUris( + String isolateId, + List uris, { + bool? local, + }) => wrapInErrorHandlerAsync( + 'lookupResolvedPackageUris', + () => _lookupResolvedPackageUris(isolateId, uris), + ); + + Future _lookupResolvedPackageUris( + String isolateId, + List uris, + ) async { + await isInitialized; + return UriList(uris: uris.map(DartUri.toResolvedUri).toList()); + } + + /// Stream controller for resume events after restart. + final _resumeAfterRestartEventsController = + StreamController.broadcast(); + + /// Stream of resume events after restart. + Stream get resumeAfterRestartEventsStream => + _resumeAfterRestartEventsController.stream; + + /// Whether there's a pending restart. + bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; + + /// Whether isolates should pause on start. + bool get pauseIsolatesOnStart => + _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false; + + /// Resumes execution of the isolate. + @override + Future resume(String isolateId, {String? step, int? frameIndex}) => + wrapInErrorHandlerAsync( + 'resume', + () => _resume(isolateId, step: step, frameIndex: frameIndex), + ); + + Future _resume( + String isolateId, { + String? step, + int? frameIndex, + }) async { + // Prevent multiple resume calls + if (_hasResumed && _currentPauseEvent == null) { + return Success(); + } + + _currentPauseEvent = null; + _hasResumed = true; + + // Send resume event + if (_isolateRef != null) { + final resumeEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ); + + _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + _logger.fine('Sent resume event for isolate ${_isolateRef!.id}'); + } + + // Handle restart events + if (_resumeAfterRestartEventsController.hasListener) { + _resumeAfterRestartEventsController.add(isolateId); + return Success(); + } + + return Success(); + } + + /// Schedules an auto-resume if no debugger connection is detected. + /// This prevents the app from being stuck in a paused state when running from terminal. + void _scheduleAutoResumeIfNeeded() { + print('JY - scheduling auto-resume check'); + _logger.info('Scheduling auto-resume check in 2 seconds'); + // Wait a reasonable amount of time for a debugger to connect + Timer(Duration(seconds: 2), () { + // If we're still paused and no debugger has taken control, auto-resume + if (_currentPauseEvent != null && + _currentPauseEvent!.kind == vm_service.EventKind.kPauseStart && + !_hasResumed) { + _logger.info( + 'Auto-resuming isolate after timeout (no debugger connected)', + ); + // Auto-resume the isolate + safeUnawaited(_resume(_isolateRef?.id ?? '1')); + } else { + _logger.info( + 'Auto-resume check: isolate already resumed or no pause event', + ); + } + }); + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +/// Extended ReloadReport that includes additional metadata in JSON output. +class _ReloadReportWithMetadata extends vm_service.ReloadReport { + final List? notices; + _ReloadReportWithMetadata({super.success, this.notices}); + + @override + Map toJson() { + final jsonified = { + 'type': 'ReloadReport', + 'success': success ?? false, + }; + if (notices != null) { + jsonified['notices'] = notices!.map((e) => {'message': e}).toList(); + } + return jsonified; + } +} diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index b2c6d5326..b75ef44c9 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '24.3.11'; +const packageVersion = '24.3.12-wip'; diff --git a/dwds/lib/src/web_socket_dwds_vm_client.dart b/dwds/lib/src/web_socket_dwds_vm_client.dart new file mode 100644 index 000000000..f4d3e9962 --- /dev/null +++ b/dwds/lib/src/web_socket_dwds_vm_client.dart @@ -0,0 +1,196 @@ +// Copyright (c) 2025, 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. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:dwds/src/services/web_socket_debug_service.dart'; +import 'package:dwds/src/services/web_socket_proxy_service.dart'; +import 'package:logging/logging.dart'; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service_interface/vm_service_interface.dart'; + +final _logger = Logger('WebSocketDwdsVmClient'); + +typedef VmRequest = Map; +typedef VmResponse = Map; + +enum _NamespacedServiceExtension { + extDwdsEmitEvent(method: 'ext.dwds.emitEvent'), + extDwdsReload(method: 'ext.dwds.reload'), + extDwdsRestart(method: 'ext.dwds.restart'), + extDwdsScreenshot(method: 'ext.dwds.screenshot'), + extDwdsSendEvent(method: 'ext.dwds.sendEvent'), + flutterListViews(method: '_flutter.listViews'); + + const _NamespacedServiceExtension({required this.method}); + final String method; +} + +/// WebSocket-based DWDS VM client. +/// +/// Provides VM service functionality without Chrome dependencies. +class WebSocketDwdsVmClient { + final VmService client; + final StreamController _requestController; + final StreamController _responseController; + Future? _closed; + + WebSocketDwdsVmClient( + this.client, + this._requestController, + this._responseController, + ); + + Future close() => + _closed ??= () async { + await _requestController.close(); + await _responseController.close(); + await client.dispose(); + }(); + + static Future create( + WebSocketDebugService debugService, + ) async { + _logger.fine('Creating WebSocket DWDS VM client'); + final webSocketProxyService = debugService.webSocketProxyService; + final responseController = StreamController(); + final responseSink = responseController.sink; + final responseStream = responseController.stream.asBroadcastStream(); + final requestController = StreamController(); + final requestSink = requestController.sink; + final requestStream = requestController.stream; + + _setUpVmServerConnection( + webSocketProxyService: webSocketProxyService, + debugService: debugService, + responseStream: responseStream, + responseSink: responseSink, + requestStream: requestStream, + requestSink: requestSink, + ); + + final client = _setUpVmClient( + responseStream: responseStream, + requestController: requestController, + requestSink: requestSink, + ); + + _logger.fine('WebSocket DWDS VM client created successfully'); + return WebSocketDwdsVmClient(client, requestController, responseController); + } + + static VmService _setUpVmClient({ + required Stream responseStream, + required StreamSink requestSink, + required StreamController requestController, + }) { + final client = VmService(responseStream.map(jsonEncode), (request) { + if (requestController.isClosed) { + _logger.warning( + 'Attempted to send a request but the connection is closed:\n\n$request', + ); + return; + } + requestSink.add(Map.from(jsonDecode(request))); + }); + return client; + } + + static void _setUpVmServerConnection({ + required WebSocketProxyService webSocketProxyService, + required WebSocketDebugService debugService, + required Stream responseStream, + required StreamSink responseSink, + required Stream requestStream, + required StreamSink requestSink, + }) { + responseStream.listen((request) async { + final response = await _maybeHandleServiceExtensionRequest( + request, + webSocketProxyService: webSocketProxyService, + ); + if (response != null) { + requestSink.add(response); + } + }); + + final vmServerConnection = VmServerConnection( + requestStream, + responseSink, + debugService.serviceExtensionRegistry, + webSocketProxyService, + ); + + // Register service extensions + for (final extension in _NamespacedServiceExtension.values) { + _logger.finest('Registering service extension: ${extension.method}'); + debugService.serviceExtensionRegistry.registerExtension( + extension.method, + vmServerConnection, + ); + } + } + + static Future _maybeHandleServiceExtensionRequest( + VmResponse request, { + required WebSocketProxyService webSocketProxyService, + }) async { + VmRequest? response; + final method = request['method']; + + _logger.finest('Processing service extension method: $method'); + + if (method == _NamespacedServiceExtension.flutterListViews.method) { + response = await _flutterListViewsHandler(webSocketProxyService); + } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { + response = _extDwdsEmitEventHandler(request); + } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { + response = {'result': 'Reload not implemented'}; + } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { + response = _extDwdsSendEventHandler(request); + } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { + response = {'result': 'Screenshot not implemented'}; + } + + if (response != null) { + response['id'] = request['id'] as String; + response['jsonrpc'] = '2.0'; + } + return response; + } + + static Future> _flutterListViewsHandler( + WebSocketProxyService webSocketProxyService, + ) async { + final vm = await webSocketProxyService.getVM(); + _logger.finest('Retrieved VM with ${vm.isolates?.length ?? 0} isolates'); + final isolates = vm.isolates; + return { + 'result': { + 'views': [ + for (final isolate in isolates ?? []) + {'id': isolate.id, 'isolate': isolate.toJson()}, + ], + }, + }; + } + + static Map _extDwdsEmitEventHandler(VmResponse request) { + final event = request['params'] as Map?; + if (event != null) { + final type = event['type'] as String?; + final payload = event['payload'] as Map?; + if (type != null && payload != null) { + _logger.fine('EmitEvent: $type $payload'); + } + } + return {'result': 'EmitEvent handled'}; + } + + static Map _extDwdsSendEventHandler(VmResponse request) { + _logger.fine('SendEvent: $request'); + return {'result': 'SendEvent handled'}; + } +} diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index ae2f792ab..248b8ffc1 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 24.3.11 +version: 24.3.12-wip description: >- A service that proxies between the Chrome debug protocol and the Dart VM diff --git a/dwds/web/client.dart b/dwds/web/client.dart index 16daa027a..0c947dd6e 100644 --- a/dwds/web/client.dart +++ b/dwds/web/client.dart @@ -19,6 +19,8 @@ import 'package:dwds/data/hot_reload_response.dart'; import 'package:dwds/data/register_event.dart'; import 'package:dwds/data/run_request.dart'; import 'package:dwds/data/serializers.dart'; +import 'package:dwds/data/service_extension_request.dart'; +import 'package:dwds/data/service_extension_response.dart'; import 'package:dwds/shared/batched_stream.dart'; import 'package:dwds/src/sockets.dart'; import 'package:http/browser_client.dart'; @@ -210,6 +212,8 @@ Future? main() { ); } else if (event is HotReloadRequest) { await handleWebSocketHotReloadRequest(event, manager, client.sink); + } else if (event is ServiceExtensionRequest) { + await handleServiceExtensionRequest(event, client.sink, manager); } }, onError: (error) { @@ -249,10 +253,6 @@ Future? main() { } } else { _sendConnectRequest(client.sink); - // TODO(yjessy): Remove this when the DWDS WebSocket connection is implemented. - if (useDwdsWebSocketConnection) { - runMain(); - } } _launchCommunicationWithDebugExtension(); }, @@ -418,6 +418,30 @@ void _sendHotReloadResponse( ); } +void _sendServiceExtensionResponse( + StreamSink clientSink, + String requestId, { + bool success = true, + String? errorMessage, + int? errorCode, + Map? result, +}) { + _trySendEvent( + clientSink, + jsonEncode( + serializers.serialize( + ServiceExtensionResponse.fromResult( + id: requestId, + success: success, + errorMessage: errorMessage, + errorCode: errorCode, + result: result, + ), + ), + ), + ); +} + Future handleWebSocketHotReloadRequest( HotReloadRequest event, ReloadingManager manager, @@ -438,6 +462,44 @@ Future handleWebSocketHotReloadRequest( } } +Future handleServiceExtensionRequest( + ServiceExtensionRequest request, + StreamSink clientSink, + ReloadingManager manager, +) async { + try { + final result = await manager.handleServiceExtension( + request.method, + request.args, + ); + + if (result != null) { + _sendServiceExtensionResponse( + clientSink, + request.id, + success: true, + result: result, + ); + } else { + // Service extension not supported by this restarter type + _sendServiceExtensionResponse( + clientSink, + request.id, + success: false, + errorMessage: 'Service extension not supported', + errorCode: -32601, // Method not found + ); + } + } catch (e) { + _sendServiceExtensionResponse( + clientSink, + request.id, + success: false, + errorMessage: e.toString(), + ); + } +} + @JS(r'$dartAppId') external String get dartAppId; @@ -490,10 +552,6 @@ external String get reloadConfiguration; @JS(r'$dartEntrypointPath') external String get dartEntrypointPath; -// TODO(yjessy): Remove this when the DWDS WebSocket connection is implemented. -@JS(r'$useDwdsWebSocketConnection') -external bool get useDwdsWebSocketConnection; - @JS(r'$dwdsEnableDevToolsLaunch') external bool get dwdsEnableDevToolsLaunch; diff --git a/dwds/web/reloader/ddc_library_bundle_restarter.dart b/dwds/web/reloader/ddc_library_bundle_restarter.dart index 77f2eab9f..545886782 100644 --- a/dwds/web/reloader/ddc_library_bundle_restarter.dart +++ b/dwds/web/reloader/ddc_library_bundle_restarter.dart @@ -37,6 +37,13 @@ extension type _Debugger._(JSObject _) implements JSObject { await invokeExtension(method, '{}').toDart; } } + + Future maybeInvokeFlutterReassemble() async { + final method = 'ext.flutter.reassemble'; + if (extensionNames.toDart.contains(method.toJS)) { + await invokeExtension(method, '{}').toDart; + } + } } @JS('XMLHttpRequest') @@ -132,4 +139,27 @@ class DdcLibraryBundleRestarter implements Restarter { ); return librariesToReload; } + + /// Handles service extension requests using the dart dev embedder + Future?> handleServiceExtension( + String method, + Map args, + ) async { + if (method == 'ext.flutter.reassemble') { + await _dartDevEmbedder.debugger.maybeInvokeFlutterReassemble(); + return {'status': 'reassemble invoked'}; + } else if (method == 'getExtensionRpcs') { + final rpcs = + _dartDevEmbedder.debugger.extensionNames.toDart.cast(); + return {'rpcs': rpcs}; + } else { + // For other extension methods, delegate to the debugger + final params = args.isNotEmpty ? jsonEncode(args) : '{}'; + final resultJson = + await _dartDevEmbedder.debugger + .invokeExtension(method, params) + .toDart; + return jsonDecode(resultJson.toDart) as Map; + } + } } diff --git a/dwds/web/reloader/manager.dart b/dwds/web/reloader/manager.dart index 1142d8016..8617f777d 100644 --- a/dwds/web/reloader/manager.dart +++ b/dwds/web/reloader/manager.dart @@ -10,6 +10,7 @@ import 'package:dwds/data/serializers.dart'; import 'package:dwds/src/sockets.dart'; import 'package:web/web.dart'; +import 'ddc_library_bundle_restarter.dart'; import 'restarter.dart'; class ReloadingManager { @@ -68,6 +69,19 @@ class ReloadingManager { window.location.reload(); } + /// Handles service extension requests by delegating to the appropriate restarter + Future?> handleServiceExtension( + String method, + Map args, + ) async { + final restarter = _restarter; + if (restarter is DdcLibraryBundleRestarter) { + return restarter.handleServiceExtension(method, args); + } + // For other restarter types, return null to indicate not supported + return null; + } + void _afterRestart(bool succeeded) { if (!succeeded) return; // Notify package:dwds that the isolate has been created. From 2707a414986dee5a4db947a6b3a2d07ff1626d23 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 26 Jun 2025 13:45:53 -0400 Subject: [PATCH 02/33] fix issue with run main at start --- .../services/web_socket_proxy_service.dart | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index 86974ba48..7f30d9130 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -125,9 +125,22 @@ class WebSocketProxyService implements VmServiceInterface { if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); + // Set up appConnection.onStart listener (like Chrome flow does) + appConn.onStart.then((_) { + // Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume + if (pauseIsolatesOnStart && !_hasResumed) { + final resumeEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: isolateRef, + ); + _hasResumed = true; + _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + } + }); + // Send pause event if enabled if (pauseIsolatesOnStart) { - print('JY - isolate paused on start - sending pause event'); final pauseEvent = vm_service.Event( kind: vm_service.EventKind.kPauseStart, timestamp: timestamp, @@ -142,7 +155,6 @@ class WebSocketProxyService implements VmServiceInterface { // This handles the case where the app is run from terminal without debug extension _scheduleAutoResumeIfNeeded(); } else { - print('JY - isolate not paused on start - sending resume event'); // If we're not pausing on start, immediately send a resume event // to ensure the app knows it can start running _logger.info('Not pausing on start, sending immediate resume event'); @@ -592,6 +604,11 @@ class WebSocketProxyService implements VmServiceInterface { String? step, int? frameIndex, }) async { + // Check if we should trigger runMain instead + if (!_hasResumed && _currentPauseEvent != null) { + appConnection.runMain(); + } + // Prevent multiple resume calls if (_hasResumed && _currentPauseEvent == null) { return Success(); @@ -624,7 +641,6 @@ class WebSocketProxyService implements VmServiceInterface { /// Schedules an auto-resume if no debugger connection is detected. /// This prevents the app from being stuck in a paused state when running from terminal. void _scheduleAutoResumeIfNeeded() { - print('JY - scheduling auto-resume check'); _logger.info('Scheduling auto-resume check in 2 seconds'); // Wait a reasonable amount of time for a debugger to connect Timer(Duration(seconds: 2), () { From 489dbcc36134104ac90121ab254838c6df44c381 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 26 Jun 2025 14:05:02 -0400 Subject: [PATCH 03/33] updated changelog --- dwds/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index ef2ab9164..2269ab02f 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,6 +1,6 @@ ## 24.3.12-wip -- Removed DWDS requirement for Chrome Debug Port by implementing a WebSocket-based communication protocol that provides essential developer tooling (hot reload, service extensions) when Chrome debugger access is unavailable. - [#2605](https://github.com/dart-lang/webdev/issues/2605) +- Implemented a WebSocket-based communication protocol that provides essential developer tooling (hot reload, service extensions) when Chrome debugger access is unavailable. - [#2605](https://github.com/dart-lang/webdev/issues/2605) - Added WebSocket-based hot reload and service extension support via new `WebSocketProxyService` class that implements VM service protocol over WebSockets. - Created new files: `WebSocketProxyService`, `WebSocketDebugService`, `WebSocketAppDebugServices`, and `WebSocketDwdsVmClient` to support socket-based DWDS functionality. - Enhanced `DevHandler` with `useWebSocketConnection` flag to toggle between Chrome-based and WebSocket-based communication protocols. From a4e0dd0ddad5a6d820d9205271f90e92eb0a3943 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 27 Jun 2025 16:00:36 -0400 Subject: [PATCH 04/33] simulate debugExtension in websocket-based execution flow --- dwds/lib/dart_web_debug_service.dart | 41 +-------- dwds/lib/src/handlers/dev_handler.dart | 91 ++++++++++++++++--- .../services/web_socket_proxy_service.dart | 88 +++++++++++++++--- 3 files changed, 156 insertions(+), 64 deletions(-) diff --git a/dwds/lib/dart_web_debug_service.dart b/dwds/lib/dart_web_debug_service.dart index 31705ca3d..587e8af43 100644 --- a/dwds/lib/dart_web_debug_service.dart +++ b/dwds/lib/dart_web_debug_service.dart @@ -62,47 +62,10 @@ class Dwds { Future debugConnection(AppConnection appConnection) async { if (!_enableDebugging) throw StateError('Debugging is not enabled.'); - final appDebugServices = await _devHandler.loadAppServices(appConnection); - - // Initialize the appropriate proxy service based on connection type if (_useDwdsWebSocketConnection) { - await _initializeWebSocketService(appDebugServices); + return await _devHandler.createDebugConnectionForWebSocket(appConnection); } else { - await _initializeChromeService(appDebugServices); - } - - return DebugConnection(appDebugServices); - } - - /// Initializes and waits for WebSocket proxy service to be ready. - Future _initializeWebSocketService(dynamic appDebugServices) async { - try { - final webSocketProxyService = appDebugServices.webSocketProxyService; - if (webSocketProxyService != null) { - await webSocketProxyService.isInitialized; - _logger.fine('WebSocket proxy service initialized successfully'); - } else { - _logger.warning('WebSocket proxy service is null'); - } - } catch (e) { - _logger.severe('Failed to initialize WebSocket proxy service: $e'); - rethrow; - } - } - - /// Initializes and waits for Chrome proxy service to be ready. - Future _initializeChromeService(dynamic appDebugServices) async { - try { - final chromeProxyService = appDebugServices.chromeProxyService; - if (chromeProxyService != null) { - await chromeProxyService.isInitialized; - _logger.fine('Chrome proxy service initialized successfully'); - } else { - _logger.warning('Chrome proxy service is null'); - } - } catch (e) { - _logger.severe('Failed to initialize Chrome proxy service: $e'); - rethrow; + return await _devHandler.createDebugConnectionForChrome(appConnection); } } diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index fd12faf4b..23817b677 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -403,20 +403,25 @@ class DevHandler { ) async { if (message == null) return; + final appId = connection.request.appId; + final wsService = _servicesByAppId[appId]?.webSocketProxyService; + + if (wsService == null) { + _logger.warning( + 'No WebSocketProxyService found for appId: $appId to process $message', + ); + return; + } if (message is HotReloadResponse) { - _servicesByAppId[connection.request.appId]?.webSocketProxyService - ?.completeHotReload(message); + wsService.completeHotReload(message); } else if (message is ServiceExtensionResponse) { - final appId = connection.request.appId; - final wsService = _servicesByAppId[appId]?.webSocketProxyService; - - if (wsService != null) { - wsService.completeServiceExtension(message); - } else { - _logger.warning( - 'No WebSocketProxyService found for appId: $appId to complete service extension', - ); - } + wsService.completeServiceExtension(message); + } else if (message is RegisterEvent) { + wsService.parseRegisterEvent(message); + } else if (message is BatchedDebugEvents) { + wsService.parseBatchedDebugEvents(message); + } else if (message is DebugEvent) { + wsService.parseDebugEvent(message); } else { throw UnsupportedError( 'Message type ${message.runtimeType} is not supported in WebSocket mode', @@ -559,6 +564,47 @@ class DevHandler { ); } + /// Creates a debug connection for WebSocket mode. + Future createDebugConnectionForWebSocket( + AppConnection appConnection, + ) async { + final appDebugServices = await loadAppServices(appConnection); + + // Initialize WebSocket proxy service + final webSocketProxyService = appDebugServices.webSocketProxyService; + if (webSocketProxyService != null) { + await webSocketProxyService.isInitialized; + _logger.fine('WebSocket proxy service initialized successfully'); + } else { + _logger.warning('WebSocket proxy service is null'); + } + + return DebugConnection(appDebugServices); + } + + /// Creates a debug connection for Chrome mode. + Future createDebugConnectionForChrome( + AppConnection appConnection, + ) async { + final appDebugServices = await loadAppServices(appConnection); + + // Initialize Chrome proxy service + try { + final chromeProxyService = appDebugServices.chromeProxyService; + if (chromeProxyService != null) { + await chromeProxyService.isInitialized; + _logger.fine('Chrome proxy service initialized successfully'); + } else { + _logger.warning('Chrome proxy service is null'); + } + } catch (e) { + _logger.severe('Failed to initialize Chrome proxy service: $e'); + rethrow; + } + + return DebugConnection(appDebugServices); + } + /// Handles connection requests for both Chrome and WebSocket modes. Future _handleConnectRequest( ConnectRequest message, @@ -600,9 +646,30 @@ class DevHandler { isWebSocketMode, ); } else { + // Complete the readyToRunMainCompleter immediately since we're + // creating a new connection. // If this is the initial app connection, we can run the app's main() // method immediately. readyToRunMainCompleter.complete(); + + // For WebSocket mode, we need to proactively create and emit a debug connection + // since Flutter tools won't call debugConnection() for WebServerDevice + if (isWebSocketMode) { + try { + // This will call loadAppServices() and initialize the WebSocket service + final debugConnection = await createDebugConnectionForWebSocket( + connection, + ); + + // Emit the debug connection through the extension stream + // This should trigger Flutter tools to pick it up as if it was an extension connection + extensionDebugConnections.add(debugConnection); + } catch (e, s) { + _logger.warning( + 'Failed to create WebSocket debug connection: $e\n$s', + ); + } + } } _appConnectionByAppId[message.appId] = connection; _connectedApps.add(connection); diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index 7f30d9130..078efe3b2 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -3,9 +3,12 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; +import 'package:dwds/data/debug_event.dart'; import 'package:dwds/data/hot_reload_request.dart'; import 'package:dwds/data/hot_reload_response.dart'; +import 'package:dwds/data/register_event.dart'; import 'package:dwds/data/service_extension_request.dart'; import 'package:dwds/data/service_extension_response.dart'; import 'package:dwds/src/connections/app_connection.dart'; @@ -126,18 +129,20 @@ class WebSocketProxyService implements VmServiceInterface { if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); // Set up appConnection.onStart listener (like Chrome flow does) - appConn.onStart.then((_) { - // Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume - if (pauseIsolatesOnStart && !_hasResumed) { - final resumeEvent = vm_service.Event( - kind: vm_service.EventKind.kResume, - timestamp: DateTime.now().millisecondsSinceEpoch, - isolate: isolateRef, - ); - _hasResumed = true; - _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); - } - }); + safeUnawaited( + appConn.onStart.then((_) { + // Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume + if (pauseIsolatesOnStart && !_hasResumed) { + final resumeEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: isolateRef, + ); + _hasResumed = true; + _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + } + }), + ); // Send pause event if enabled if (pauseIsolatesOnStart) { @@ -523,6 +528,63 @@ class WebSocketProxyService implements VmServiceInterface { } } + /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service + /// protocol [Event]. + void parseRegisterEvent(RegisterEvent registerEvent) { + _logger.fine('Parsing RegisterEvent: ${registerEvent.eventData}'); + + if (!_isIsolateRunning || _isolateRef == null) { + _logger.warning('Cannot register service extension - no isolate running'); + return; + } + + final service = registerEvent.eventData; + + // Add the service to the isolate's extension RPCs if we had access to the isolate + // In WebSocket mode, we don't maintain the full isolate object like Chrome mode, + // but we can still emit the ServiceExtensionAdded event for tooling + + final event = vm_service.Event( + kind: vm_service.EventKind.kServiceExtensionAdded, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ); + event.extensionRPC = service; + + _streamNotify(vm_service.EventStreams.kIsolate, event); + _logger.fine('Emitted ServiceExtensionAdded event for: $service'); + } + + /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service + /// protocol [Event]s. + void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) { + for (final debugEvent in debugEvents.events) { + parseDebugEvent(debugEvent); + } + } + + /// Parses the [DebugEvent] and emits a corresponding Dart VM Service + /// protocol [Event]. + void parseDebugEvent(DebugEvent debugEvent) { + if (!_isIsolateRunning || _isolateRef == null) { + _logger.warning('Cannot parse debug event - no isolate running'); + return; + } + + _streamNotify( + vm_service.EventStreams.kExtension, + vm_service.Event( + kind: vm_service.EventKind.kExtension, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ) + ..extensionKind = debugEvent.kind + ..extensionData = vm_service.ExtensionData.parse( + jsonDecode(debugEvent.eventData) as Map, + ), + ); + } + @override Future setFlag(String name, String value) => wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value)); @@ -608,7 +670,7 @@ class WebSocketProxyService implements VmServiceInterface { if (!_hasResumed && _currentPauseEvent != null) { appConnection.runMain(); } - + // Prevent multiple resume calls if (_hasResumed && _currentPauseEvent == null) { return Success(); From 412bda310a23c113fa4ce7c6687dd9b6992c8455 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 11 Jul 2025 14:30:54 -0400 Subject: [PATCH 05/33] added multiwindow support --- dwds/lib/src/handlers/dev_handler.dart | 61 ++-- .../services/web_socket_debug_service.dart | 3 +- .../services/web_socket_proxy_service.dart | 321 ++++++++++++------ 3 files changed, 264 insertions(+), 121 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 23817b677..e9e76469a 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -140,21 +140,29 @@ class DevHandler { } /// Sends the provided [request] to all connected injected clients. - void _sendRequestToClients(Object request) { - _logger.finest('Sending request to injected clients: $request'); + /// Returns the number of clients the request was successfully sent to. + int _sendRequestToClients(Object request) { + var successfulSends = 0; for (final injectedConnection in _injectedConnections) { try { injectedConnection.sink.add(jsonEncode(serializers.serialize(request))); + successfulSends++; } on StateError catch (e) { // The sink has already closed (app is disconnected), or another StateError occurred. _logger.warning( - 'Failed to send request to client, connection likely closed. Error: $e', + 'Failed to send request to client ${injectedConnection.hashCode}, ' + 'connection likely closed. Error: $e', ); } catch (e, s) { // Catch any other potential errors during sending. - _logger.severe('Error sending request to client: $e', e, s); + _logger.severe( + 'Error sending request to client ${injectedConnection.hashCode}: $e', + e, + s, + ); } } + return successfulSends; } /// Starts a [DebugService] for local debugging. @@ -625,20 +633,38 @@ class DevHandler { readyToRunMainCompleter.future, ); - // We can take over a connection if there is no connectedInstanceId (this - // means the client completely disconnected), or if the existing - // AppConnection is in the KeepAlive state (this means it disconnected but - // is still waiting for a possible reconnect - this happens during a page - // reload). - final canReuseConnection = - services != null && - (services.connectedInstanceId == null || - existingConnection?.isInKeepAlivePeriod == true); + // Determine whether to reuse existing services or create new ones + // This handles both page refresh (same instance) and new browser window scenarios + final bool canReuseConnection; + if (isWebSocketMode) { + // WebSocket mode: Allow connection reuse for page refreshes and same instance reconnections + canReuseConnection = + services != null && + ((existingConnection != null && + (existingConnection.isInKeepAlivePeriod == true || + existingConnection.request.instanceId == + message.instanceId)) || + (services.connectedInstanceId == null && + (existingConnection == null || + existingConnection.request.instanceId == + message.instanceId))); + } else { + // Chrome mode: More restrictive reuse logic + // We can take over a connection if there is no connectedInstanceId (this + // means the client completely disconnected), or if the existing + // AppConnection is in the KeepAlive state (this means it disconnected but + // is still waiting for a possible reconnect - this happens during a page + // reload). + canReuseConnection = + services != null && + (services.connectedInstanceId == null || + existingConnection?.isInKeepAlivePeriod == true); + } if (canReuseConnection) { // Reconnect to existing service. await _reconnectToService( - services, + services!, existingConnection, connection, message, @@ -646,17 +672,14 @@ class DevHandler { isWebSocketMode, ); } else { - // Complete the readyToRunMainCompleter immediately since we're - // creating a new connection. - // If this is the initial app connection, we can run the app's main() - // method immediately. + // New browser window or initial connection: run main() immediately readyToRunMainCompleter.complete(); // For WebSocket mode, we need to proactively create and emit a debug connection // since Flutter tools won't call debugConnection() for WebServerDevice if (isWebSocketMode) { try { - // This will call loadAppServices() and initialize the WebSocket service + // Initialize the WebSocket service and create debug connection final debugConnection = await createDebugConnectionForWebSocket( connection, ); diff --git a/dwds/lib/src/services/web_socket_debug_service.dart b/dwds/lib/src/services/web_socket_debug_service.dart index 3a3b403ab..c4f995822 100644 --- a/dwds/lib/src/services/web_socket_debug_service.dart +++ b/dwds/lib/src/services/web_socket_debug_service.dart @@ -19,7 +19,8 @@ import 'package:vm_service_interface/vm_service_interface.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; /// Defines callbacks for sending messages to the connected client. -typedef SendClientRequest = void Function(Object request); +/// Returns the number of clients the request was successfully sent to. +typedef SendClientRequest = int Function(Object request); // Connection control for WebSocket clients bool _acceptNewConnections = true; diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index 078efe3b2..c79e35c99 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -23,10 +23,67 @@ import 'package:vm_service/vm_service.dart'; import 'package:vm_service_interface/vm_service_interface.dart'; /// Defines callbacks for sending messages to the connected client. -typedef SendClientRequest = void Function(Object request); +/// Returns the number of clients the request was successfully sent to. +typedef SendClientRequest = int Function(Object request); const _pauseIsolatesOnStartFlag = 'pause_isolates_on_start'; +/// Tracks hot reload responses from multiple browser windows/tabs. +class _HotReloadTracker { + final String requestId; + final Completer completer; + final int expectedResponses; + final List responses = []; + final Timer timeoutTimer; + + _HotReloadTracker({ + required this.requestId, + required this.completer, + required this.expectedResponses, + required this.timeoutTimer, + }); + + bool get isComplete => responses.length >= expectedResponses; + + void addResponse(HotReloadResponse response) { + responses.add(response); + } + + bool get allSuccessful => responses.every((r) => r.success); + + void dispose() { + timeoutTimer.cancel(); + } +} + +/// Tracks service extension responses from multiple browser windows/tabs. +class _ServiceExtensionTracker { + final String requestId; + final Completer completer; + final int expectedResponses; + final List responses = []; + final Timer timeoutTimer; + + _ServiceExtensionTracker({ + required this.requestId, + required this.completer, + required this.expectedResponses, + required this.timeoutTimer, + }); + + bool get isComplete => responses.length >= expectedResponses; + + void addResponse(ServiceExtensionResponse response) { + responses.add(response); + } + + bool get allSuccessful => responses.every((r) => r.success == true); + + void dispose() { + timeoutTimer.cancel(); + } +} + /// WebSocket-based VM service proxy for web debugging. /// /// Provides hot reload and service extension support via WebSocket communication. @@ -37,9 +94,9 @@ class WebSocketProxyService implements VmServiceInterface { Future get isInitialized => _initializedCompleter.future; Completer _initializedCompleter = Completer(); - /// Active service extension requests by ID. - final Map> - _pendingServiceExtensions = {}; + /// Active service extension trackers by request ID. + final Map _pendingServiceExtensionTrackers = + {}; /// Sends messages to the client. final SendClientRequest sendClientRequest; @@ -47,8 +104,8 @@ class WebSocketProxyService implements VmServiceInterface { /// App connection for this service. final AppConnection appConnection; - /// Current hot reload request (one at a time). - Completer? _pendingHotReload; + /// Active hot reload trackers by request ID. + final Map _pendingHotReloads = {}; /// App connection cleanup subscription. StreamSubscription? _appConnectionDoneSubscription; @@ -77,8 +134,6 @@ class WebSocketProxyService implements VmServiceInterface { bool get _isIsolateRunning => _isolateRunning; /// Creates a new isolate for WebSocket debugging. - /// - /// Destroys existing isolate first if present. Call [destroyIsolate] on restart. Future createIsolate([AppConnection? appConnectionOverride]) async { final appConn = appConnectionOverride ?? appConnection; @@ -128,10 +183,9 @@ class WebSocketProxyService implements VmServiceInterface { if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); - // Set up appConnection.onStart listener (like Chrome flow does) + // Set up app connection listener for resume handling safeUnawaited( appConn.onStart.then((_) { - // Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume if (pauseIsolatesOnStart && !_hasResumed) { final resumeEvent = vm_service.Event( kind: vm_service.EventKind.kResume, @@ -154,15 +208,11 @@ class WebSocketProxyService implements VmServiceInterface { _currentPauseEvent = pauseEvent; _hasResumed = false; _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); - // Flutter tools will call resume() to start the app - // Auto-resume after a short delay if no debugger is connected - // This handles the case where the app is run from terminal without debug extension + // Auto-resume if no debugger connects _scheduleAutoResumeIfNeeded(); } else { - // If we're not pausing on start, immediately send a resume event - // to ensure the app knows it can start running - _logger.info('Not pausing on start, sending immediate resume event'); + // Send immediate resume event final resumeEvent = vm_service.Event( kind: vm_service.EventKind.kResume, timestamp: timestamp, @@ -277,7 +327,7 @@ class WebSocketProxyService implements VmServiceInterface { ); } - /// Returns a broadcast stream for the given streamId, creating if needed. + /// Returns a broadcast stream for the given streamId. @override Stream onEvent(String streamId) { return _streamControllers.putIfAbsent(streamId, () { @@ -318,7 +368,6 @@ class WebSocketProxyService implements VmServiceInterface { final controller = _streamControllers[streamId]; if (controller != null && !controller.isClosed) { controller.add(event); - _logger.fine('Added event to stream $streamId: $event'); } else { _logger.warning('Cannot add event to closed/missing stream: $streamId'); } @@ -356,13 +405,12 @@ class WebSocketProxyService implements VmServiceInterface { Future getVM() => wrapInErrorHandlerAsync('getVM', _getVM); Future _getVM() { - // On web, we do not currently support isolates, so isInitialized is not required. return captureElapsedTime(() async { return _vm; }, (result) => DwdsEvent.getVM()); } - /// Throws error if remoteDebugger is accessed (not available in WebSocket mode). + /// Not available in WebSocket mode. dynamic get remoteDebugger { throw UnsupportedError( 'remoteDebugger not available in WebSocketProxyService.\n' @@ -404,60 +452,93 @@ class WebSocketProxyService implements VmServiceInterface { } } - /// Completes hot reload with response. + /// Completes hot reload with response from client. void completeHotReload(HotReloadResponse response) { - final completer = _pendingHotReload; - _pendingHotReload = null; + final tracker = _pendingHotReloads[response.id]; + + if (tracker == null) { + _logger.warning( + 'Received hot reload response but no pending tracker found (id: ${response.id})', + ); + return; + } + + tracker.addResponse(response); - if (completer != null) { - if (response.success) { - completer.complete(response); + if (tracker.isComplete) { + _pendingHotReloads.remove(response.id); + tracker.dispose(); + + if (tracker.allSuccessful) { + tracker.completer.complete(response); } else { - completer.completeError( - response.errorMessage ?? 'Unknown client error during hot reload', + final failedResponses = tracker.responses.where((r) => !r.success); + final errorMessages = failedResponses + .map((r) => r.errorMessage ?? 'Unknown error') + .join('; '); + tracker.completer.completeError( + 'Hot reload failed in some clients: $errorMessages', ); } - } else { - _logger.warning( - 'Received hot reload response but no pending completer found (id: ${response.id})', - ); } } /// Performs WebSocket-based hot reload. Future _performWebSocketHotReload({String? requestId}) async { - if (_pendingHotReload != null) { - throw StateError('Hot reload already pending'); - } - final id = requestId ?? createId(); - final completer = Completer(); - _pendingHotReload = completer; + + // Check if there's already a pending hot reload with this ID + if (_pendingHotReloads.containsKey(id)) { + throw StateError('Hot reload already pending for ID: $id'); + } const timeout = Duration(seconds: 10); _logger.info('Sending HotReloadRequest with ID ($id) to client'); - await Future.microtask(() { - sendClientRequest(HotReloadRequest((b) => b.id = id)); + // Send the request and get the number of connected clients + final clientCount = await Future.microtask(() { + return sendClientRequest(HotReloadRequest((b) => b.id = id)); }); - try { - final response = await completer.future.timeout( - timeout, - onTimeout: () { - _pendingHotReload = null; - throw TimeoutException( - 'Client did not respond to hot reload', - timeout, + if (clientCount == 0) { + throw StateError('No clients available for hot reload'); + } + + // Create tracker for this hot reload request + final completer = Completer(); + final timeoutTimer = Timer(timeout, () { + final tracker = _pendingHotReloads.remove(id); + if (tracker != null) { + tracker.dispose(); + if (!completer.isCompleted) { + completer.completeError( + TimeoutException( + 'Hot reload timed out - received ${tracker.responses.length}/$clientCount responses', + timeout, + ), ); - }, - ); + } + } + }); + final tracker = _HotReloadTracker( + requestId: id, + completer: completer, + expectedResponses: clientCount, + timeoutTimer: timeoutTimer, + ); + + _pendingHotReloads[id] = tracker; + + try { + final response = await completer.future; if (!response.success) { throw Exception(response.errorMessage ?? 'Client reported failure'); } } catch (e) { - _pendingHotReload = null; + // Clean up tracker if still present + final remainingTracker = _pendingHotReloads.remove(id); + remainingTracker?.dispose(); rethrow; } } @@ -479,12 +560,13 @@ class WebSocketProxyService implements VmServiceInterface { Map? args, }) async { final requestId = createId(); - if (_pendingServiceExtensions.containsKey(requestId)) { - throw StateError('Service extension call already pending for this ID'); - } - final completer = Completer(); - _pendingServiceExtensions[requestId] = completer; + // Check if there's already a pending service extension with this ID + if (_pendingServiceExtensionTrackers.containsKey(requestId)) { + throw StateError( + 'Service extension call already pending for ID: $requestId', + ); + } final request = ServiceExtensionRequest.fromArgs( id: requestId, @@ -492,47 +574,100 @@ class WebSocketProxyService implements VmServiceInterface { args: args != null ? Map.from(args) : {}, ); - sendClientRequest(request); - final response = await completer.future.timeout(Duration(seconds: 10)); - _pendingServiceExtensions.remove(requestId); + // Send the request and get the number of connected clients + final clientCount = sendClientRequest(request); + + if (clientCount == 0) { + throw StateError('No clients available for service extension'); + } + + // Create tracker for this service extension request + const timeout = Duration(seconds: 10); + final completer = Completer(); + final timeoutTimer = Timer(timeout, () { + final tracker = _pendingServiceExtensionTrackers.remove(requestId); + if (tracker != null) { + tracker.dispose(); + if (!completer.isCompleted) { + completer.completeError( + TimeoutException( + 'Service extension $method timed out - received ${tracker.responses.length}/$clientCount responses', + timeout, + ), + ); + } + } + }); + + final tracker = _ServiceExtensionTracker( + requestId: requestId, + completer: completer, + expectedResponses: clientCount, + timeoutTimer: timeoutTimer, + ); + + _pendingServiceExtensionTrackers[requestId] = tracker; - if (response.errorMessage != null) { - throw RPCError( - method, - response.errorCode ?? RPCErrorKind.kServerError.code, - response.errorMessage!, + try { + final response = await completer.future; + + if (response.errorMessage != null) { + throw RPCError( + method, + response.errorCode ?? RPCErrorKind.kServerError.code, + response.errorMessage!, + ); + } + return Response()..json = response.result; + } catch (e) { + // Clean up tracker if still present + final remainingTracker = _pendingServiceExtensionTrackers.remove( + requestId, ); + remainingTracker?.dispose(); + rethrow; } - return Response()..json = response.result; } /// Completes service extension with response. void completeServiceExtension(ServiceExtensionResponse response) { final id = response.id; - final completer = _pendingServiceExtensions.remove(id); - if (completer != null) { - if (response.success == true) { - completer.complete(response); + final tracker = _pendingServiceExtensionTrackers[id]; + + if (tracker == null) { + _logger.warning( + 'No pending tracker found for service extension (id: $id)', + ); + return; + } + + tracker.addResponse(response); + + if (tracker.isComplete) { + _pendingServiceExtensionTrackers.remove(id); + tracker.dispose(); + + if (tracker.allSuccessful) { + tracker.completer.complete(response); } else { - completer.completeError( - response.errorMessage ?? - 'Unknown client error during service extension', + final failedResponses = tracker.responses.where( + (r) => r.success != true, + ); + final errorMessages = failedResponses + .map((r) => r.errorMessage ?? 'Unknown error') + .join('; '); + tracker.completer.completeError( + 'Service extension failed in some clients: $errorMessages', ); } - } else { - _logger.warning( - 'No pending completer found for service extension (id: $id)', - ); } } /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service /// protocol [Event]. void parseRegisterEvent(RegisterEvent registerEvent) { - _logger.fine('Parsing RegisterEvent: ${registerEvent.eventData}'); - if (!_isIsolateRunning || _isolateRef == null) { _logger.warning('Cannot register service extension - no isolate running'); return; @@ -540,10 +675,7 @@ class WebSocketProxyService implements VmServiceInterface { final service = registerEvent.eventData; - // Add the service to the isolate's extension RPCs if we had access to the isolate - // In WebSocket mode, we don't maintain the full isolate object like Chrome mode, - // but we can still emit the ServiceExtensionAdded event for tooling - + // Emit ServiceExtensionAdded event for tooling final event = vm_service.Event( kind: vm_service.EventKind.kServiceExtensionAdded, timestamp: DateTime.now().millisecondsSinceEpoch, @@ -552,7 +684,6 @@ class WebSocketProxyService implements VmServiceInterface { event.extensionRPC = service; _streamNotify(vm_service.EventStreams.kIsolate, event); - _logger.fine('Emitted ServiceExtensionAdded event for: $service'); } /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service @@ -598,11 +729,11 @@ class WebSocketProxyService implements VmServiceInterface { final oldValue = _currentVmServiceFlags[name]; _currentVmServiceFlags[name] = value == 'true'; - // Handle pause_isolates_on_start flag + // Handle pause_isolates_on_start flag changes if (name == _pauseIsolatesOnStartFlag && value == 'true' && oldValue == false) { - // Send pause event for existing isolate if it wasn't paused initially + // Send pause event for existing isolate if not already paused if (_isIsolateRunning && _isolateRef != null && _currentPauseEvent == null) { @@ -666,7 +797,7 @@ class WebSocketProxyService implements VmServiceInterface { String? step, int? frameIndex, }) async { - // Check if we should trigger runMain instead + // Trigger runMain if this is the first resume if (!_hasResumed && _currentPauseEvent != null) { appConnection.runMain(); } @@ -688,37 +819,25 @@ class WebSocketProxyService implements VmServiceInterface { ); _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); - _logger.fine('Sent resume event for isolate ${_isolateRef!.id}'); } // Handle restart events if (_resumeAfterRestartEventsController.hasListener) { _resumeAfterRestartEventsController.add(isolateId); - return Success(); } return Success(); } - /// Schedules an auto-resume if no debugger connection is detected. - /// This prevents the app from being stuck in a paused state when running from terminal. + /// Schedules auto-resume if no debugger connects within timeout. void _scheduleAutoResumeIfNeeded() { - _logger.info('Scheduling auto-resume check in 2 seconds'); - // Wait a reasonable amount of time for a debugger to connect + // Wait for a debugger to connect, then auto-resume if still paused Timer(Duration(seconds: 2), () { - // If we're still paused and no debugger has taken control, auto-resume if (_currentPauseEvent != null && _currentPauseEvent!.kind == vm_service.EventKind.kPauseStart && !_hasResumed) { - _logger.info( - 'Auto-resuming isolate after timeout (no debugger connected)', - ); - // Auto-resume the isolate + _logger.info('Auto-resuming isolate (no debugger connected)'); safeUnawaited(_resume(_isolateRef?.id ?? '1')); - } else { - _logger.info( - 'Auto-resume check: isolate already resumed or no pause event', - ); } }); } From 8f7c6452c11e4eb06938c50a0a5eff338a69425c Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 15 Jul 2025 14:32:08 -0400 Subject: [PATCH 06/33] refactoring and removing unused method --- dwds/lib/src/handlers/dev_handler.dart | 165 +++++++++++------- .../services/web_socket_proxy_service.dart | 21 --- 2 files changed, 99 insertions(+), 87 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index e9e76469a..5fee89f2a 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -344,7 +344,6 @@ class DevHandler { appConnection = await _handleConnectRequest( message, injectedConnection, - isWebSocketMode: useWebSocketConnection, ); } else { final connection = appConnection; @@ -613,12 +612,15 @@ class DevHandler { return DebugConnection(appDebugServices); } - /// Handles connection requests for both Chrome and WebSocket modes. Future _handleConnectRequest( ConnectRequest message, - SocketConnection sseConnection, { - required bool isWebSocketMode, - }) async { + SocketConnection sseConnection, + ) async { + if (useWebSocketConnection) { + return _handleWebSocketConnectRequest(message, sseConnection); + } + + // Original Chrome logic from dart-lang/webdev // After a page refresh, reconnect to the same app services if they // were previously launched and create the new isolate. final services = _servicesByAppId[message.appId]; @@ -633,43 +635,90 @@ class DevHandler { readyToRunMainCompleter.future, ); - // Determine whether to reuse existing services or create new ones - // This handles both page refresh (same instance) and new browser window scenarios - final bool canReuseConnection; - if (isWebSocketMode) { - // WebSocket mode: Allow connection reuse for page refreshes and same instance reconnections - canReuseConnection = - services != null && - ((existingConnection != null && - (existingConnection.isInKeepAlivePeriod == true || - existingConnection.request.instanceId == - message.instanceId)) || - (services.connectedInstanceId == null && - (existingConnection == null || - existingConnection.request.instanceId == - message.instanceId))); + // We can take over a connection if there is no connectedInstanceId (this + // means the client completely disconnected), or if the existing + // AppConnection is in the KeepAlive state (this means it disconnected but + // is still waiting for a possible reconnect - this happens during a page + // reload). + final canReuseConnection = + services != null && + (services.connectedInstanceId == null || + existingConnection?.isInKeepAlivePeriod == true); + + if (canReuseConnection) { + // Disconnect any old connection (eg. those in the keep-alive waiting + // state when reloading the page). + existingConnection?.shutDown(); + services.chromeProxyService.destroyIsolate(); + + // Reconnect to existing service. + services.connectedInstanceId = message.instanceId; + + if (services.chromeProxyService.pauseIsolatesOnStart) { + // If the pause-isolates-on-start flag is set, we need to wait for + // the resume event to run the app's main() method. + _waitForResumeEventToRunMain( + services.chromeProxyService.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } else { + // Otherwise, we can run the app's main() method immediately. + readyToRunMainCompleter.complete(); + } + + await services.chromeProxyService.createIsolate(connection); } else { - // Chrome mode: More restrictive reuse logic - // We can take over a connection if there is no connectedInstanceId (this - // means the client completely disconnected), or if the existing - // AppConnection is in the KeepAlive state (this means it disconnected but - // is still waiting for a possible reconnect - this happens during a page - // reload). - canReuseConnection = - services != null && - (services.connectedInstanceId == null || - existingConnection?.isInKeepAlivePeriod == true); + // If this is the initial app connection, we can run the app's main() + // method immediately. + readyToRunMainCompleter.complete(); } + _appConnectionByAppId[message.appId] = connection; + _connectedApps.add(connection); + return connection; + } + + /// Handles WebSocket mode connection requests with multi-window support. + Future _handleWebSocketConnectRequest( + ConnectRequest message, + SocketConnection sseConnection, + ) async { + // After a page refresh, reconnect to the same app services if they + // were previously launched and create the new isolate. + final services = _servicesByAppId[message.appId]; + final existingConnection = _appConnectionByAppId[message.appId]; + // Completer to indicate when the app's main() method is ready to be run. + // Its future is passed to the AppConnection so that it can be awaited on + // before running the app's main() method: + final readyToRunMainCompleter = Completer(); + final connection = AppConnection( + message, + sseConnection, + readyToRunMainCompleter.future, + ); + + // WebSocket mode: Allow connection reuse for page refreshes and same instance reconnections + final canReuseConnection = + services != null && + ( + // Case 1: Existing connection can be reused if in keep-alive or same instance + (existingConnection != null && + (existingConnection.isInKeepAlivePeriod == true || + existingConnection.request.instanceId == + message.instanceId)) || + // Case 2: No active service connection, allow if no existing conn or same instance + (services.connectedInstanceId == null && + (existingConnection == null || + existingConnection.request.instanceId == + message.instanceId))); if (canReuseConnection) { // Reconnect to existing service. await _reconnectToService( - services!, + services, existingConnection, connection, message, readyToRunMainCompleter, - isWebSocketMode, ); } else { // New browser window or initial connection: run main() immediately @@ -677,21 +726,17 @@ class DevHandler { // For WebSocket mode, we need to proactively create and emit a debug connection // since Flutter tools won't call debugConnection() for WebServerDevice - if (isWebSocketMode) { - try { - // Initialize the WebSocket service and create debug connection - final debugConnection = await createDebugConnectionForWebSocket( - connection, - ); + try { + // Initialize the WebSocket service and create debug connection + final debugConnection = await createDebugConnectionForWebSocket( + connection, + ); - // Emit the debug connection through the extension stream - // This should trigger Flutter tools to pick it up as if it was an extension connection - extensionDebugConnections.add(debugConnection); - } catch (e, s) { - _logger.warning( - 'Failed to create WebSocket debug connection: $e\n$s', - ); - } + // Emit the debug connection through the extension stream + // This should trigger Flutter tools to pick it up as if it was an extension connection + extensionDebugConnections.add(debugConnection); + } catch (e, s) { + _logger.warning('Failed to create WebSocket debug connection: $e\n$s'); } } _appConnectionByAppId[message.appId] = connection; @@ -700,38 +745,26 @@ class DevHandler { return connection; } - /// Handles reconnection to existing services. + /// Handles reconnection to existing services for web-socket mode. Future _reconnectToService( IAppDebugServices services, AppConnection? existingConnection, AppConnection newConnection, ConnectRequest message, Completer readyToRunMainCompleter, - bool isWebSocketMode, ) async { // Disconnect old connection existingConnection?.shutDown(); services.connectedInstanceId = message.instanceId; - if (isWebSocketMode) { - services.webSocketProxyService?.destroyIsolate(); - _logger.finest('WebSocket service reconnected for app: ${message.appId}'); + services.webSocketProxyService?.destroyIsolate(); + _logger.finest('WebSocket service reconnected for app: ${message.appId}'); - _setupMainExecution( - services.webSocketProxyService?.pauseIsolatesOnStart == true, - services.webSocketProxyService?.resumeAfterRestartEventsStream, - readyToRunMainCompleter, - ); - } else { - services.chromeProxyService.destroyIsolate(); - _logger.finest('Chrome service reconnected for app: ${message.appId}'); - - _setupMainExecution( - services.chromeProxyService.pauseIsolatesOnStart, - services.chromeProxyService.resumeAfterRestartEventsStream, - readyToRunMainCompleter, - ); - } + _setupMainExecution( + services.webSocketProxyService?.pauseIsolatesOnStart == true, + services.webSocketProxyService?.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); await _handleIsolateStart(newConnection); } diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index c79e35c99..a084d8c3a 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -208,9 +208,6 @@ class WebSocketProxyService implements VmServiceInterface { _currentPauseEvent = pauseEvent; _hasResumed = false; _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); - - // Auto-resume if no debugger connects - _scheduleAutoResumeIfNeeded(); } else { // Send immediate resume event final resumeEvent = vm_service.Event( @@ -821,27 +818,9 @@ class WebSocketProxyService implements VmServiceInterface { _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); } - // Handle restart events - if (_resumeAfterRestartEventsController.hasListener) { - _resumeAfterRestartEventsController.add(isolateId); - } - return Success(); } - /// Schedules auto-resume if no debugger connects within timeout. - void _scheduleAutoResumeIfNeeded() { - // Wait for a debugger to connect, then auto-resume if still paused - Timer(Duration(seconds: 2), () { - if (_currentPauseEvent != null && - _currentPauseEvent!.kind == vm_service.EventKind.kPauseStart && - !_hasResumed) { - _logger.info('Auto-resuming isolate (no debugger connected)'); - safeUnawaited(_resume(_isolateRef?.id ?? '1')); - } - }); - } - @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } From 91c7b415ed1ef6e9b5117c19bfdd3f8532b06022 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 16 Jul 2025 15:07:24 -0400 Subject: [PATCH 07/33] support page refresh from vsCode --- dwds/lib/src/handlers/dev_handler.dart | 16 +- .../services/web_socket_proxy_service.dart | 148 +++++++++++++----- 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 5fee89f2a..48080f82c 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -341,10 +341,13 @@ class DevHandler { 'https://github.com/dart-lang/webdev/issues/new.', ); } - appConnection = await _handleConnectRequest( - message, - injectedConnection, - ); + appConnection = + useWebSocketConnection + ? await _handleWebSocketConnectRequest( + message, + injectedConnection, + ) + : await _handleConnectRequest(message, injectedConnection); } else { final connection = appConnection; if (connection == null) { @@ -616,11 +619,6 @@ class DevHandler { ConnectRequest message, SocketConnection sseConnection, ) async { - if (useWebSocketConnection) { - return _handleWebSocketConnectRequest(message, sseConnection); - } - - // Original Chrome logic from dart-lang/webdev // After a page refresh, reconnect to the same app services if they // were previously launched and create the new isolate. final services = _servicesByAppId[message.appId]; diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index a084d8c3a..bfcaef7d2 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -16,7 +16,6 @@ import 'package:dwds/src/events.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:logging/logging.dart'; -// Ensure RPCError and RPCErrorKind are available for error handling import 'package:pub_semver/pub_semver.dart' as semver; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart'; @@ -85,8 +84,6 @@ class _ServiceExtensionTracker { } /// WebSocket-based VM service proxy for web debugging. -/// -/// Provides hot reload and service extension support via WebSocket communication. class WebSocketProxyService implements VmServiceInterface { final _logger = Logger('WebSocketProxyService'); @@ -102,7 +99,7 @@ class WebSocketProxyService implements VmServiceInterface { final SendClientRequest sendClientRequest; /// App connection for this service. - final AppConnection appConnection; + AppConnection appConnection; /// Active hot reload trackers by request ID. final Map _pendingHotReloads = {}; @@ -118,6 +115,26 @@ class WebSocketProxyService implements VmServiceInterface { _pauseIsolatesOnStartFlag: false, }; + /// Stream controller for resume events after restart. + final _resumeAfterRestartEventsController = + StreamController.broadcast(); + + /// Stream of resume events after restart. + Stream get resumeAfterRestartEventsStream => + _resumeAfterRestartEventsController.stream; + + /// Whether there's a pending restart. + bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; + + /// Whether isolates should pause on start. + bool get pauseIsolatesOnStart => + _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false; + + /// Counter for generating unique isolate IDs across page refreshes + static int _globalIsolateIdCounter = 0; + + bool get _isIsolateRunning => _isolateRunning; + /// Root VM instance. final vm_service.VM _vm; @@ -131,12 +148,19 @@ class WebSocketProxyService implements VmServiceInterface { vm_service.Event? _currentPauseEvent; bool _hasResumed = false; - bool get _isIsolateRunning => _isolateRunning; - /// Creates a new isolate for WebSocket debugging. Future createIsolate([AppConnection? appConnectionOverride]) async { final appConn = appConnectionOverride ?? appConnection; + // Update the app connection reference if a new one is provided + if (appConnectionOverride != null) { + _logger.fine( + 'Updating appConnection reference from ' + 'instanceId: ${appConnection.request.instanceId} to instanceId: ${appConnectionOverride.request.instanceId}', + ); + appConnection = appConnectionOverride; + } + // Clean up existing isolate if (_isIsolateRunning) { destroyIsolate(); @@ -149,11 +173,12 @@ class WebSocketProxyService implements VmServiceInterface { destroyIsolate(); }); - // Create isolate reference + // Create isolate reference with unique ID that changes on each page refresh + final isolateId = '${++_globalIsolateIdCounter}'; final isolateRef = vm_service.IsolateRef( - id: '1', + id: isolateId, name: 'main()', - number: '1', + number: isolateId, isSystemIsolate: false, ); @@ -766,21 +791,6 @@ class WebSocketProxyService implements VmServiceInterface { return UriList(uris: uris.map(DartUri.toResolvedUri).toList()); } - /// Stream controller for resume events after restart. - final _resumeAfterRestartEventsController = - StreamController.broadcast(); - - /// Stream of resume events after restart. - Stream get resumeAfterRestartEventsStream => - _resumeAfterRestartEventsController.stream; - - /// Whether there's a pending restart. - bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; - - /// Whether isolates should pause on start. - bool get pauseIsolatesOnStart => - _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false; - /// Resumes execution of the isolate. @override Future resume(String isolateId, {String? step, int? frameIndex}) => @@ -794,31 +804,87 @@ class WebSocketProxyService implements VmServiceInterface { String? step, int? frameIndex, }) async { - // Trigger runMain if this is the first resume - if (!_hasResumed && _currentPauseEvent != null) { + if (hasPendingRestart && !_resumeAfterRestartEventsController.isClosed) { + _resumeAfterRestartEventsController.add(isolateId); + } else { appConnection.runMain(); } + return Success(); + } - // Prevent multiple resume calls - if (_hasResumed && _currentPauseEvent == null) { - return Success(); - } + @override + Future lookupPackageUris(String isolateId, List uris) => + wrapInErrorHandlerAsync( + 'lookupPackageUris', + () => _lookupPackageUris(isolateId, uris), + ); - _currentPauseEvent = null; - _hasResumed = true; + Future _lookupPackageUris( + String isolateId, + List uris, + ) async { + await isInitialized; + return UriList(uris: uris.map(DartUri.toPackageUri).toList()); + } - // Send resume event - if (_isolateRef != null) { - final resumeEvent = vm_service.Event( - kind: vm_service.EventKind.kResume, - timestamp: DateTime.now().millisecondsSinceEpoch, - isolate: _isolateRef!, - ); + @override + Future registerService(String service, String alias) { + return _rpcNotSupportedFuture('registerService'); + } - _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + @override + Future getFlagList() => + wrapInErrorHandlerAsync('getFlagList', _getFlagList); + + Future _getFlagList() async { + // Return basic flag list for WebSocket mode + return FlagList( + flags: [ + Flag( + name: _pauseIsolatesOnStartFlag, + comment: 'If enabled, isolates are paused on start', + valueAsString: pauseIsolatesOnStart.toString(), + ), + ], + ); + } + + @override + Future getStack( + String isolateId, { + String? idZoneId, + int? limit, + }) => wrapInErrorHandlerAsync( + 'getStack', + () => _getStack(isolateId, idZoneId: idZoneId, limit: limit), + ); + + Future _getStack( + String isolateId, { + String? idZoneId, + int? limit, + }) async { + if (!_isIsolateRunning || _isolateRef == null) { + throw vm_service.RPCError( + 'getStack', + vm_service.RPCErrorKind.kInvalidParams.code, + 'No running isolate found for id: $isolateId', + ); + } + if (_isolateRef!.id != isolateId) { + throw vm_service.RPCError( + 'getStack', + vm_service.RPCErrorKind.kInvalidParams.code, + 'Isolate with id $isolateId not found.', + ); } - return Success(); + // Return empty stack since we're in WebSocket mode without Chrome debugging + return vm_service.Stack( + frames: [], + asyncCausalFrames: [], + awaiterFrames: [], + ); } @override From 871f3881bd9b12fafdbb629fe2f1831aa5100848 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 17 Jul 2025 12:14:54 -0400 Subject: [PATCH 08/33] refactor createisolate and resume method --- .../services/web_socket_proxy_service.dart | 62 ++++++------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index bfcaef7d2..1b85881f4 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -146,20 +146,11 @@ class WebSocketProxyService implements VmServiceInterface { vm_service.IsolateRef? _isolateRef; bool _isolateRunning = false; vm_service.Event? _currentPauseEvent; - bool _hasResumed = false; /// Creates a new isolate for WebSocket debugging. Future createIsolate([AppConnection? appConnectionOverride]) async { - final appConn = appConnectionOverride ?? appConnection; - - // Update the app connection reference if a new one is provided - if (appConnectionOverride != null) { - _logger.fine( - 'Updating appConnection reference from ' - 'instanceId: ${appConnection.request.instanceId} to instanceId: ${appConnectionOverride.request.instanceId}', - ); - appConnection = appConnectionOverride; - } + // Update app connection if override provided + appConnection = appConnectionOverride ?? appConnection; // Clean up existing isolate if (_isIsolateRunning) { @@ -169,7 +160,9 @@ class WebSocketProxyService implements VmServiceInterface { // Auto-cleanup on connection close await _appConnectionDoneSubscription?.cancel(); - _appConnectionDoneSubscription = appConn.onDone.asStream().listen((_) { + _appConnectionDoneSubscription = appConnection.onDone.asStream().listen(( + _, + ) { destroyIsolate(); }); @@ -184,7 +177,6 @@ class WebSocketProxyService implements VmServiceInterface { _isolateRef = isolateRef; _isolateRunning = true; - _hasResumed = false; _vm.isolates?.add(isolateRef); final timestamp = DateTime.now().millisecondsSinceEpoch; @@ -206,23 +198,6 @@ class WebSocketProxyService implements VmServiceInterface { ), ); - if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); - - // Set up app connection listener for resume handling - safeUnawaited( - appConn.onStart.then((_) { - if (pauseIsolatesOnStart && !_hasResumed) { - final resumeEvent = vm_service.Event( - kind: vm_service.EventKind.kResume, - timestamp: DateTime.now().millisecondsSinceEpoch, - isolate: isolateRef, - ); - _hasResumed = true; - _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); - } - }), - ); - // Send pause event if enabled if (pauseIsolatesOnStart) { final pauseEvent = vm_service.Event( @@ -231,18 +206,11 @@ class WebSocketProxyService implements VmServiceInterface { isolate: isolateRef, ); _currentPauseEvent = pauseEvent; - _hasResumed = false; _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); - } else { - // Send immediate resume event - final resumeEvent = vm_service.Event( - kind: vm_service.EventKind.kResume, - timestamp: timestamp, - isolate: isolateRef, - ); - _hasResumed = true; - _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); } + + // Complete initialization after isolate is set up + if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); } /// Destroys the isolate and cleans up state. @@ -273,7 +241,6 @@ class WebSocketProxyService implements VmServiceInterface { _isolateRef = null; _isolateRunning = false; _currentPauseEvent = null; - _hasResumed = false; if (_initializedCompleter.isCompleted) { _initializedCompleter = Completer(); @@ -765,7 +732,6 @@ class WebSocketProxyService implements VmServiceInterface { isolate: _isolateRef!, ); _currentPauseEvent = pauseEvent; - _hasResumed = false; _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); } } @@ -809,6 +775,18 @@ class WebSocketProxyService implements VmServiceInterface { } else { appConnection.runMain(); } + + // Clear pause state and send resume event to notify debugging tools + if (_currentPauseEvent != null && _isolateRef != null) { + _currentPauseEvent = null; + final resumeEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ); + _streamNotify(vm_service.EventStreams.kDebug, resumeEvent); + } + return Success(); } From d7dad515c0bd315023b4d238ef029fbba0138ea5 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 17 Jul 2025 12:42:37 -0400 Subject: [PATCH 09/33] implemented pause/resume logic --- .../services/web_socket_proxy_service.dart | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index 1b85881f4..bc7434090 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -146,6 +146,7 @@ class WebSocketProxyService implements VmServiceInterface { vm_service.IsolateRef? _isolateRef; bool _isolateRunning = false; vm_service.Event? _currentPauseEvent; + bool _mainHasStarted = false; /// Creates a new isolate for WebSocket debugging. Future createIsolate([AppConnection? appConnectionOverride]) async { @@ -241,6 +242,7 @@ class WebSocketProxyService implements VmServiceInterface { _isolateRef = null; _isolateRunning = false; _currentPauseEvent = null; + _mainHasStarted = false; if (_initializedCompleter.isCompleted) { _initializedCompleter = Completer(); @@ -757,6 +759,26 @@ class WebSocketProxyService implements VmServiceInterface { return UriList(uris: uris.map(DartUri.toResolvedUri).toList()); } + /// Pauses execution of the isolate. + @override + Future pause(String isolateId) => + wrapInErrorHandlerAsync('pause', () => _pause(isolateId)); + + Future _pause(String isolateId) async { + // Create a pause event and store it + if (_isolateRef != null) { + final pauseEvent = vm_service.Event( + kind: vm_service.EventKind.kPauseInterrupted, + timestamp: DateTime.now().millisecondsSinceEpoch, + isolate: _isolateRef!, + ); + _currentPauseEvent = pauseEvent; + _streamNotify(vm_service.EventStreams.kDebug, pauseEvent); + } + + return Success(); + } + /// Resumes execution of the isolate. @override Future resume(String isolateId, {String? step, int? frameIndex}) => @@ -773,7 +795,18 @@ class WebSocketProxyService implements VmServiceInterface { if (hasPendingRestart && !_resumeAfterRestartEventsController.isClosed) { _resumeAfterRestartEventsController.add(isolateId); } else { - appConnection.runMain(); + if (!_mainHasStarted) { + try { + appConnection.runMain(); + _mainHasStarted = true; + } catch (e) { + if (e.toString().contains('Main has already started')) { + _mainHasStarted = true; + } else { + rethrow; + } + } + } } // Clear pause state and send resume event to notify debugging tools From 6f40567a3a121d0c19c5a6c1b7aafa80364aa6ec Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 17 Jul 2025 13:14:53 -0400 Subject: [PATCH 10/33] improve canReuseConnection logic --- dwds/lib/src/handlers/dev_handler.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 48080f82c..b59acfa1a 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -696,18 +696,7 @@ class DevHandler { // WebSocket mode: Allow connection reuse for page refreshes and same instance reconnections final canReuseConnection = - services != null && - ( - // Case 1: Existing connection can be reused if in keep-alive or same instance - (existingConnection != null && - (existingConnection.isInKeepAlivePeriod == true || - existingConnection.request.instanceId == - message.instanceId)) || - // Case 2: No active service connection, allow if no existing conn or same instance - (services.connectedInstanceId == null && - (existingConnection == null || - existingConnection.request.instanceId == - message.instanceId))); + services != null && services.connectedInstanceId == null; if (canReuseConnection) { // Reconnect to existing service. From b4d5cb1b7926670bcf6c30f7a078b9114d463a0e Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 17 Jul 2025 13:20:19 -0400 Subject: [PATCH 11/33] comments cleanup --- dwds/lib/src/services/web_socket_app_debug_services.dart | 2 -- dwds/lib/src/services/web_socket_debug_service.dart | 2 -- dwds/lib/src/web_socket_dwds_vm_client.dart | 2 -- 3 files changed, 6 deletions(-) diff --git a/dwds/lib/src/services/web_socket_app_debug_services.dart b/dwds/lib/src/services/web_socket_app_debug_services.dart index 4d8539238..5f1d400dd 100644 --- a/dwds/lib/src/services/web_socket_app_debug_services.dart +++ b/dwds/lib/src/services/web_socket_app_debug_services.dart @@ -8,8 +8,6 @@ import 'package:dwds/src/services/web_socket_debug_service.dart'; import 'package:dwds/src/web_socket_dwds_vm_client.dart'; /// WebSocket-based implementation of app debug services. -/// -/// Provides debugging capabilities without Chrome dependencies. class WebSocketAppDebugServices implements IAppDebugServices { final WebSocketDebugService _debugService; final WebSocketDwdsVmClient _dwdsVmClient; diff --git a/dwds/lib/src/services/web_socket_debug_service.dart b/dwds/lib/src/services/web_socket_debug_service.dart index c4f995822..23d3106cd 100644 --- a/dwds/lib/src/services/web_socket_debug_service.dart +++ b/dwds/lib/src/services/web_socket_debug_service.dart @@ -27,8 +27,6 @@ bool _acceptNewConnections = true; int _clientsConnected = 0; /// WebSocket-based debug service for web debugging. -/// -/// Provides hot reload and service extension support without Chrome dependencies. class WebSocketDebugService { final String hostname; final int port; diff --git a/dwds/lib/src/web_socket_dwds_vm_client.dart b/dwds/lib/src/web_socket_dwds_vm_client.dart index f4d3e9962..e9cc29360 100644 --- a/dwds/lib/src/web_socket_dwds_vm_client.dart +++ b/dwds/lib/src/web_socket_dwds_vm_client.dart @@ -29,8 +29,6 @@ enum _NamespacedServiceExtension { } /// WebSocket-based DWDS VM client. -/// -/// Provides VM service functionality without Chrome dependencies. class WebSocketDwdsVmClient { final VmService client; final StreamController _requestController; From 0edf3d89ef73adc02810a1e3c7bb93538aa31520 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 17 Jul 2025 15:15:38 -0400 Subject: [PATCH 12/33] fixed canReuseConnection logic --- dwds/lib/src/handlers/dev_handler.dart | 14 ++++++++++++-- .../lib/src/services/web_socket_proxy_service.dart | 6 ------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index b59acfa1a..6226d6fc6 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -694,9 +694,19 @@ class DevHandler { readyToRunMainCompleter.future, ); - // WebSocket mode: Allow connection reuse for page refreshes and same instance reconnections + // Allow connection reuse for page refreshes and same instance reconnections + final isSameInstance = + existingConnection?.request.instanceId == message.instanceId; + final isKeepAliveReconnect = + existingConnection?.isInKeepAlivePeriod == true; + final hasNoActiveConnection = services?.connectedInstanceId == null; + final noExistingConnection = existingConnection == null; + final canReuseConnection = - services != null && services.connectedInstanceId == null; + services != null && + (isSameInstance || + (isKeepAliveReconnect && hasNoActiveConnection) || + (noExistingConnection && hasNoActiveConnection)); if (canReuseConnection) { // Reconnect to existing service. diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index bc7434090..ca181e9c6 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -153,12 +153,6 @@ class WebSocketProxyService implements VmServiceInterface { // Update app connection if override provided appConnection = appConnectionOverride ?? appConnection; - // Clean up existing isolate - if (_isIsolateRunning) { - destroyIsolate(); - await Future.delayed(Duration(milliseconds: 10)); - } - // Auto-cleanup on connection close await _appConnectionDoneSubscription?.cancel(); _appConnectionDoneSubscription = appConnection.onDone.asStream().listen(( From 340baadc69140e3d7cf10f82ea74be83230161b3 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 18 Jul 2025 13:42:51 -0400 Subject: [PATCH 13/33] implemented _handleConnectionClosed and fix issue with getVM --- dwds/lib/src/handlers/dev_handler.dart | 5 +- .../services/web_socket_proxy_service.dart | 112 ++++++++++++++++-- 2 files changed, 103 insertions(+), 14 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 6226d6fc6..a01fc702c 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -754,7 +754,6 @@ class DevHandler { existingConnection?.shutDown(); services.connectedInstanceId = message.instanceId; - services.webSocketProxyService?.destroyIsolate(); _logger.finest('WebSocket service reconnected for app: ${message.appId}'); _setupMainExecution( @@ -800,7 +799,9 @@ class DevHandler { final appId = appConnection.request.appId; if (useWebSocketConnection) { - _servicesByAppId[appId]?.webSocketProxyService?.destroyIsolate(); + _logger.fine( + 'Isolate exit handled by WebSocket proxy service for app: $appId', + ); } else { _servicesByAppId[appId]?.chromeProxyService.destroyIsolate(); } diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index ca181e9c6..ac342f18d 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -104,8 +104,12 @@ class WebSocketProxyService implements VmServiceInterface { /// Active hot reload trackers by request ID. final Map _pendingHotReloads = {}; - /// App connection cleanup subscription. - StreamSubscription? _appConnectionDoneSubscription; + /// App connection cleanup subscriptions by connection instance ID. + final Map> _appConnectionDoneSubscriptions = + {}; + + /// Active connection count for this service. + int _activeConnectionCount = 0; /// Event stream controllers. final Map> _streamControllers = {}; @@ -153,15 +157,42 @@ class WebSocketProxyService implements VmServiceInterface { // Update app connection if override provided appConnection = appConnectionOverride ?? appConnection; + // Track this connection + final connectionId = appConnection.request.instanceId; + + // Check if this connection is already being tracked + final isNewConnection = + !_appConnectionDoneSubscriptions.containsKey(connectionId); + + if (isNewConnection) { + _activeConnectionCount++; + _logger.fine( + 'Adding new connection: $connectionId (total: $_activeConnectionCount)', + ); + } else { + _logger.fine( + 'Reconnecting existing connection: $connectionId (total: $_activeConnectionCount)', + ); + } + // Auto-cleanup on connection close - await _appConnectionDoneSubscription?.cancel(); - _appConnectionDoneSubscription = appConnection.onDone.asStream().listen(( - _, - ) { - destroyIsolate(); - }); + final existingSubscription = _appConnectionDoneSubscriptions[connectionId]; + await existingSubscription?.cancel(); + _appConnectionDoneSubscriptions[connectionId] = appConnection.onDone + .asStream() + .listen((_) { + _handleConnectionClosed(connectionId); + }); + + // If we already have a running isolate, just update the connection and return + if (_isIsolateRunning && _isolateRef != null) { + _logger.fine( + 'Reusing existing isolate ${_isolateRef!.id} for connection: $connectionId', + ); + return; + } - // Create isolate reference with unique ID that changes on each page refresh + // Create isolate reference with unique ID final isolateId = '${++_globalIsolateIdCounter}'; final isolateRef = vm_service.IsolateRef( id: isolateId, @@ -175,6 +206,10 @@ class WebSocketProxyService implements VmServiceInterface { _vm.isolates?.add(isolateRef); final timestamp = DateTime.now().millisecondsSinceEpoch; + _logger.fine( + 'Created new isolate: $isolateId for connection: $connectionId', + ); + // Send lifecycle events _streamNotify( vm_service.EventStreams.kIsolate, @@ -208,15 +243,55 @@ class WebSocketProxyService implements VmServiceInterface { if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); } + /// Handles a connection being closed. + void _handleConnectionClosed(String connectionId) { + _logger.fine('Connection closed: $connectionId'); + + // Only decrement if this connection was actually being tracked + if (_appConnectionDoneSubscriptions.containsKey(connectionId)) { + // Remove the subscription for this connection + _appConnectionDoneSubscriptions[connectionId]?.cancel(); + _appConnectionDoneSubscriptions.remove(connectionId); + + // Decrease active connection count + _activeConnectionCount--; + _logger.fine( + 'Removed connection: $connectionId (remaining: $_activeConnectionCount)', + ); + + // Only destroy the isolate if there are no more active connections + if (_activeConnectionCount <= 0) { + _logger.fine('No more active connections, destroying isolate'); + destroyIsolate(); + } else { + _logger.fine( + 'Still have $_activeConnectionCount active connections, keeping isolate alive', + ); + } + } else { + _logger.warning( + 'Attempted to close connection that was not tracked: $connectionId', + ); + } + } + /// Destroys the isolate and cleans up state. void destroyIsolate() { _logger.fine('Destroying isolate'); - if (!_isIsolateRunning) return; + + if (!_isIsolateRunning) { + _logger.fine('Isolate already destroyed, ignoring'); + return; + } final isolateRef = _isolateRef; - _appConnectionDoneSubscription?.cancel(); - _appConnectionDoneSubscription = null; + // Cancel all connection subscriptions + for (final subscription in _appConnectionDoneSubscriptions.values) { + subscription.cancel(); + } + _appConnectionDoneSubscriptions.clear(); + _activeConnectionCount = 0; // Send exit event if (isolateRef != null) { @@ -391,6 +466,19 @@ class WebSocketProxyService implements VmServiceInterface { Future _getVM() { return captureElapsedTime(() async { + // Ensure the VM's isolate list is synchronized with our actual state + if (_isIsolateRunning && _isolateRef != null) { + // Make sure our isolate is in the VM's isolate list + final isolateExists = + _vm.isolates?.any((ref) => ref.id == _isolateRef!.id) ?? false; + if (!isolateExists) { + _vm.isolates?.add(_isolateRef!); + } + } else { + // If no isolate is running, make sure the list is empty + _vm.isolates?.clear(); + } + return _vm; }, (result) => DwdsEvent.getVM()); } From fa5feb0349228fa418abf7ee32b8ca66bfa7ad3a Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 18 Jul 2025 15:09:57 -0400 Subject: [PATCH 14/33] fix issue with handling isolate cleanup --- dwds/lib/src/handlers/dev_handler.dart | 11 ++++++-- .../services/web_socket_proxy_service.dart | 26 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index a01fc702c..da9fc7f97 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -162,6 +162,9 @@ class DevHandler { ); } } + _logger.fine( + 'Sent request to $successfulSends clients out of ${_injectedConnections.length} total connections', + ); return successfulSends; } @@ -392,8 +395,12 @@ class DevHandler { _injectedConnections.remove(injectedConnection); final connection = appConnection; if (connection != null) { - _appConnectionByAppId.remove(connection.request.appId); - final services = _servicesByAppId[connection.request.appId]; + final appId = connection.request.appId; + final services = _servicesByAppId[appId]; + // WebSocket mode doesn't need this because WebSocketProxyService handles connection tracking and cleanup + if (!useWebSocketConnection) { + _appConnectionByAppId.remove(appId); + } if (services != null) { if (services.connectedInstanceId == null || services.connectedInstanceId == connection.request.instanceId) { diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index ac342f18d..394df02e5 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -258,11 +258,31 @@ class WebSocketProxyService implements VmServiceInterface { _logger.fine( 'Removed connection: $connectionId (remaining: $_activeConnectionCount)', ); + _logger.fine( + 'Current tracked connections: ${_appConnectionDoneSubscriptions.keys.toList()}', + ); - // Only destroy the isolate if there are no more active connections + // Instead of destroying the isolate immediately, check if there are still + // clients that can receive hot reload requests if (_activeConnectionCount <= 0) { - _logger.fine('No more active connections, destroying isolate'); - destroyIsolate(); + // Double-check by asking the sendClientRequest callback how many clients are available + final actualClientCount = sendClientRequest({'type': 'ping'}); + _logger.fine( + 'Actual client count from sendClientRequest: $actualClientCount', + ); + + if (actualClientCount == 0) { + _logger.fine( + 'No clients available for hot reload, destroying isolate', + ); + destroyIsolate(); + } else { + _logger.fine( + 'Still have $actualClientCount clients available, keeping isolate alive', + ); + // Update our internal counter to match reality + _activeConnectionCount = actualClientCount; + } } else { _logger.fine( 'Still have $_activeConnectionCount active connections, keeping isolate alive', From afe9fc9d9435940147b78d159d92347cd5d5f0c0 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Mon, 21 Jul 2025 12:50:54 -0400 Subject: [PATCH 15/33] addressed comments regarding closing remoteDebugger --- dwds/CHANGELOG.md | 1 - .../lib/src/connections/debug_connection.dart | 26 +++---------------- .../web_socket_app_debug_services.dart | 5 +--- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index cd8fc0331..08a82aabb 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -4,7 +4,6 @@ - Added WebSocket-based hot reload and service extension support via new `WebSocketProxyService` class that implements VM service protocol over WebSockets. - Enhanced `DevHandler` with `useWebSocketConnection` flag to toggle between Chrome-based and WebSocket-based communication protocols. - ## 24.4.0 - Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests. diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart index 99fcb9b3e..c420a5dc1 100644 --- a/dwds/lib/src/connections/debug_connection.dart +++ b/dwds/lib/src/connections/debug_connection.dart @@ -22,20 +22,9 @@ class DebugConnection { Future? _closed; DebugConnection(this._appDebugServices) { - _setupChromeCloseHandler(); - } - - /// Sets up Chrome remote debugger close handler if available. - void _setupChromeCloseHandler() { - try { - final chromeProxyService = _appDebugServices.chromeProxyService; - if (chromeProxyService != null) { - final remoteDebugger = chromeProxyService.remoteDebugger; - remoteDebugger.onClose.first.then((_) => close()); - } - } catch (_) { - // Chrome proxy service not available in WebSocket-only mode - ignore - } + _appDebugServices.chromeProxyService?.remoteDebugger.onClose.first.then( + (_) => close(), + ); } /// The port of the host Dart VM Service. @@ -53,14 +42,7 @@ class DebugConnection { Future close() => _closed ??= () async { // Close Chrome remote debugger if available - try { - final chromeProxyService = _appDebugServices.chromeProxyService; - if (chromeProxyService != null) { - await chromeProxyService.remoteDebugger.close(); - } - } catch (_) { - // Chrome proxy service not available in WebSocket-only mode - ignore - } + await _appDebugServices.chromeProxyService?.remoteDebugger.close(); await _appDebugServices.close(); _onDoneCompleter.complete(); diff --git a/dwds/lib/src/services/web_socket_app_debug_services.dart b/dwds/lib/src/services/web_socket_app_debug_services.dart index 5f1d400dd..903334888 100644 --- a/dwds/lib/src/services/web_socket_app_debug_services.dart +++ b/dwds/lib/src/services/web_socket_app_debug_services.dart @@ -34,10 +34,7 @@ class WebSocketAppDebugServices implements IAppDebugServices { @override Uri? get ddsUri => null; @override - dynamic get chromeProxyService => - throw UnsupportedError( - 'Chrome proxy service not available in WebSocket-only mode', - ); + dynamic get chromeProxyService => null; @override dynamic get webSocketProxyService => _debugService.webSocketProxyService; From 18617bda18154ddde46ca7b78a9107b1c37f4154 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Mon, 21 Jul 2025 12:56:48 -0400 Subject: [PATCH 16/33] code cleanup --- dwds/lib/src/connections/debug_connection.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart index c420a5dc1..c15b8c79e 100644 --- a/dwds/lib/src/connections/debug_connection.dart +++ b/dwds/lib/src/connections/debug_connection.dart @@ -41,9 +41,7 @@ class DebugConnection { Future close() => _closed ??= () async { - // Close Chrome remote debugger if available await _appDebugServices.chromeProxyService?.remoteDebugger.close(); - await _appDebugServices.close(); _onDoneCompleter.complete(); }(); From 32f165ea8a8ae483b1d6e396e673dfc318b7def3 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Mon, 21 Jul 2025 15:30:19 -0400 Subject: [PATCH 17/33] created common interface for debugService and webSocketDebugService --- dwds/lib/src/handlers/dev_handler.dart | 1 - dwds/lib/src/services/app_debug_services.dart | 2 +- dwds/lib/src/services/debug_service.dart | 180 +++++++++++++++++- .../web_socket_app_debug_services.dart | 2 +- .../services/web_socket_debug_service.dart | 177 ----------------- dwds/lib/src/web_socket_dwds_vm_client.dart | 2 +- 6 files changed, 182 insertions(+), 182 deletions(-) delete mode 100644 dwds/lib/src/services/web_socket_debug_service.dart diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index da9fc7f97..07ccf1a17 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -34,7 +34,6 @@ import 'package:dwds/src/services/app_debug_services.dart'; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; import 'package:dwds/src/services/web_socket_app_debug_services.dart'; -import 'package:dwds/src/services/web_socket_debug_service.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:dwds/src/web_socket_dwds_vm_client.dart'; import 'package:logging/logging.dart'; diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index 3b5440164..77b83086e 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -10,7 +10,7 @@ import 'package:dwds/src/services/debug_service.dart'; /// Common interface for debug service containers. abstract class IAppDebugServices { - dynamic get debugService; + IDebugService get debugService; dynamic get dwdsVmClient; dynamic get dwdsStats; Uri? get ddsUri; diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index 34202ad09..a5982c84a 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -17,6 +17,7 @@ import 'package:dwds/src/events.dart'; import 'package:dwds/src/readers/asset_reader.dart'; import 'package:dwds/src/services/chrome_proxy_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; +import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:dwds/src/utilities/server.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:logging/logging.dart'; @@ -126,15 +127,28 @@ Future _handleSseConnections( } } +/// Common interface for debug services (Chrome or WebSocket based). +abstract class IDebugService { + String get hostname; + int get port; + String get uri; + Future get encodedUri; + ServiceExtensionRegistry get serviceExtensionRegistry; + Future close(); +} + /// A Dart Web Debug Service. /// /// Creates a [ChromeProxyService] from an existing Chrome instance. -class DebugService { +class DebugService implements IDebugService { static String? _ddsUri; final VmServiceInterface chromeProxyService; + @override final String hostname; + @override final ServiceExtensionRegistry serviceExtensionRegistry; + @override final int port; final String authToken; final HttpServer _server; @@ -162,6 +176,7 @@ class DebugService { this._urlEncoder, ); + @override Future close() => _closed ??= Future.wait([ _server.close(), @@ -183,6 +198,7 @@ class DebugService { return _dds!; } + @override String get uri { final dds = _dds; if (_spawnDds && dds != null) { @@ -200,6 +216,7 @@ class DebugService { } String? _encodedUri; + @override Future get encodedUri async { if (_encodedUri != null) return _encodedUri!; var encoded = uri; @@ -303,6 +320,167 @@ class DebugService { } } +/// Defines callbacks for sending messages to the connected client. +/// Returns the number of clients the request was successfully sent to. +typedef SendClientRequest = int Function(Object request); + +/// WebSocket-based debug service for web debugging. +class WebSocketDebugService implements IDebugService { + @override + final String hostname; + @override + final int port; + final String authToken; + final HttpServer _server; + final WebSocketProxyService _webSocketProxyService; + final ServiceExtensionRegistry _serviceExtensionRegistry; + final UrlEncoder? _urlEncoder; + + Future? _closed; + DartDevelopmentServiceLauncher? _dds; + String? _encodedUri; + + WebSocketDebugService._( + this.hostname, + this.port, + this.authToken, + this._webSocketProxyService, + this._serviceExtensionRegistry, + this._server, + this._urlEncoder, + ); + + /// Returns the WebSocketProxyService instance. + WebSocketProxyService get webSocketProxyService => _webSocketProxyService; + + /// Returns the ServiceExtensionRegistry instance. + @override + ServiceExtensionRegistry get serviceExtensionRegistry => + _serviceExtensionRegistry; + + /// Closes the debug service and associated resources. + @override + Future close() => + _closed ??= Future.wait([ + _server.close(), + if (_dds != null) _dds!.shutdown(), + ]); + + /// Starts DDS (Dart Development Service). + Future startDartDevelopmentService({ + int? ddsPort, + }) async { + const timeout = Duration(seconds: 10); + + try { + _dds = await DartDevelopmentServiceLauncher.start( + remoteVmServiceUri: Uri( + scheme: 'http', + host: hostname, + port: port, + path: authToken, + ), + serviceUri: Uri(scheme: 'http', host: hostname, port: ddsPort ?? 0), + ).timeout(timeout); + } catch (e) { + throw Exception('Failed to start DDS: $e'); + } + return _dds!; + } + + @override + String get uri => + Uri(scheme: 'ws', host: hostname, port: port, path: authToken).toString(); + + @override + Future get encodedUri async { + if (_encodedUri != null) return _encodedUri!; + var encoded = uri; + if (_urlEncoder != null) encoded = await _urlEncoder(encoded); + return _encodedUri = encoded; + } + + static Future start( + String hostname, + AppConnection appConnection, { + required SendClientRequest sendClientRequest, + UrlEncoder? urlEncoder, + }) async { + final authToken = _makeAuthToken(); + final serviceExtensionRegistry = ServiceExtensionRegistry(); + + final webSocketProxyService = await WebSocketProxyService.create( + sendClientRequest, + appConnection, + ); + + final handler = _createWebSocketHandler( + serviceExtensionRegistry, + webSocketProxyService, + ); + + final server = await startHttpServer(hostname, port: 0); + serveHttpRequests(server, handler, (e, s) { + Logger('WebSocketDebugService').warning('Error serving requests', e); + }); + + return WebSocketDebugService._( + server.address.host, + server.port, + authToken, + webSocketProxyService, + serviceExtensionRegistry, + server, + urlEncoder, + ); + } + + /// Creates the WebSocket handler for incoming connections. + static dynamic _createWebSocketHandler( + ServiceExtensionRegistry serviceExtensionRegistry, + WebSocketProxyService webSocketProxyService, + ) { + return webSocketHandler((WebSocketChannel webSocket) { + if (!_acceptNewConnections) { + webSocket.sink.add( + jsonEncode({ + 'error': 'Cannot connect: another service has taken control.', + }), + ); + webSocket.sink.close(); + return; + } + + final responseController = StreamController>(); + webSocket.sink.addStream(responseController.stream.map(jsonEncode)); + + final inputStream = webSocket.stream.map((value) { + if (value is List) { + value = utf8.decode(value); + } else if (value is! String) { + throw StateError( + 'Unexpected value type from web socket: ${value.runtimeType}', + ); + } + return Map.from(jsonDecode(value)); + }); + + ++_clientsConnected; + VmServerConnection( + inputStream, + responseController.sink, + serviceExtensionRegistry, + webSocketProxyService, + ).done.whenComplete(() { + --_clientsConnected; + if (!_acceptNewConnections && _clientsConnected == 0) { + _acceptNewConnections = true; + } + }); + }); + } +} + // Creates a random auth token for more secure connections. String _makeAuthToken() { final tokenBytes = 8; diff --git a/dwds/lib/src/services/web_socket_app_debug_services.dart b/dwds/lib/src/services/web_socket_app_debug_services.dart index 903334888..eb4799aec 100644 --- a/dwds/lib/src/services/web_socket_app_debug_services.dart +++ b/dwds/lib/src/services/web_socket_app_debug_services.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:dwds/src/services/app_debug_services.dart'; -import 'package:dwds/src/services/web_socket_debug_service.dart'; +import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/web_socket_dwds_vm_client.dart'; /// WebSocket-based implementation of app debug services. diff --git a/dwds/lib/src/services/web_socket_debug_service.dart b/dwds/lib/src/services/web_socket_debug_service.dart deleted file mode 100644 index 23d3106cd..000000000 --- a/dwds/lib/src/services/web_socket_debug_service.dart +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2025, 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:dds/dds_launcher.dart'; -import 'package:dwds/src/connections/app_connection.dart'; - -import 'package:dwds/src/services/web_socket_proxy_service.dart'; -import 'package:dwds/src/utilities/server.dart'; -import 'package:logging/logging.dart'; -import 'package:shelf_web_socket/shelf_web_socket.dart'; -import 'package:vm_service_interface/vm_service_interface.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -/// Defines callbacks for sending messages to the connected client. -/// Returns the number of clients the request was successfully sent to. -typedef SendClientRequest = int Function(Object request); - -// Connection control for WebSocket clients -bool _acceptNewConnections = true; -int _clientsConnected = 0; - -/// WebSocket-based debug service for web debugging. -class WebSocketDebugService { - final String hostname; - final int port; - final String authToken; - final HttpServer _server; - final WebSocketProxyService _webSocketProxyService; - final ServiceExtensionRegistry _serviceExtensionRegistry; - - Future? _closed; - DartDevelopmentServiceLauncher? _dds; - - WebSocketDebugService._( - this.hostname, - this.port, - this.authToken, - this._webSocketProxyService, - this._serviceExtensionRegistry, - this._server, - ); - - /// Returns the WebSocketProxyService instance. - WebSocketProxyService get webSocketProxyService => _webSocketProxyService; - - /// Returns the ServiceExtensionRegistry instance. - ServiceExtensionRegistry get serviceExtensionRegistry => - _serviceExtensionRegistry; - - /// Closes the debug service and associated resources. - Future close() => - _closed ??= Future.wait([ - _server.close(), - if (_dds != null) _dds!.shutdown(), - ]); - - /// Starts DDS (Dart Development Service). - Future startDartDevelopmentService({ - int? ddsPort, - }) async { - const timeout = Duration(seconds: 10); - - try { - _dds = await DartDevelopmentServiceLauncher.start( - remoteVmServiceUri: Uri( - scheme: 'http', - host: hostname, - port: port, - path: authToken, - ), - serviceUri: Uri(scheme: 'http', host: hostname, port: ddsPort ?? 0), - ).timeout(timeout); - } catch (e) { - throw Exception('Failed to start DDS: $e'); - } - return _dds!; - } - - String get uri => - Uri(scheme: 'ws', host: hostname, port: port, path: authToken).toString(); - - static Future start( - String hostname, - AppConnection appConnection, { - required SendClientRequest sendClientRequest, - }) async { - final authToken = _makeAuthToken(); - final serviceExtensionRegistry = ServiceExtensionRegistry(); - - final webSocketProxyService = await WebSocketProxyService.create( - sendClientRequest, - appConnection, - ); - - final handler = _createWebSocketHandler( - serviceExtensionRegistry, - webSocketProxyService, - ); - - final server = await startHttpServer(hostname, port: 0); - serveHttpRequests(server, handler, (e, s) { - Logger('WebSocketDebugService').warning('Error serving requests', e); - }); - - return WebSocketDebugService._( - server.address.host, - server.port, - authToken, - webSocketProxyService, - serviceExtensionRegistry, - server, - ); - } - - /// Creates the WebSocket handler for incoming connections. - static dynamic _createWebSocketHandler( - ServiceExtensionRegistry serviceExtensionRegistry, - WebSocketProxyService webSocketProxyService, - ) { - return webSocketHandler((WebSocketChannel webSocket) { - if (!_acceptNewConnections) { - webSocket.sink.add( - jsonEncode({ - 'error': 'Cannot connect: another service has taken control.', - }), - ); - webSocket.sink.close(); - return; - } - - final responseController = StreamController>(); - webSocket.sink.addStream(responseController.stream.map(jsonEncode)); - - final inputStream = webSocket.stream.map((value) { - if (value is List) { - value = utf8.decode(value); - } else if (value is! String) { - throw StateError( - 'Unexpected value type from web socket: ${value.runtimeType}', - ); - } - return Map.from(jsonDecode(value)); - }); - - ++_clientsConnected; - VmServerConnection( - inputStream, - responseController.sink, - serviceExtensionRegistry, - webSocketProxyService, - ).done.whenComplete(() { - --_clientsConnected; - if (!_acceptNewConnections && _clientsConnected == 0) { - _acceptNewConnections = true; - } - }); - }); - } -} - -// Creates a random auth token for more secure connections. -String _makeAuthToken() { - final tokenBytes = 8; - final bytes = Uint8List(tokenBytes); - final random = Random.secure(); - for (var i = 0; i < tokenBytes; i++) { - bytes[i] = random.nextInt(256); - } - return base64Url.encode(bytes); -} diff --git a/dwds/lib/src/web_socket_dwds_vm_client.dart b/dwds/lib/src/web_socket_dwds_vm_client.dart index e9cc29360..aaa94d07b 100644 --- a/dwds/lib/src/web_socket_dwds_vm_client.dart +++ b/dwds/lib/src/web_socket_dwds_vm_client.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:dwds/src/services/web_socket_debug_service.dart'; +import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:logging/logging.dart'; import 'package:vm_service/vm_service.dart'; From 49b234a55181cacea6ddfd1926da1399bb98d964 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 10:39:45 -0400 Subject: [PATCH 18/33] remove use of _acceptNewConnections in WebSocketDebugService --- dwds/lib/src/services/debug_service.dart | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index a5982c84a..790d48c07 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -441,16 +441,6 @@ class WebSocketDebugService implements IDebugService { WebSocketProxyService webSocketProxyService, ) { return webSocketHandler((WebSocketChannel webSocket) { - if (!_acceptNewConnections) { - webSocket.sink.add( - jsonEncode({ - 'error': 'Cannot connect: another service has taken control.', - }), - ); - webSocket.sink.close(); - return; - } - final responseController = StreamController>(); webSocket.sink.addStream(responseController.stream.map(jsonEncode)); @@ -473,9 +463,6 @@ class WebSocketDebugService implements IDebugService { webSocketProxyService, ).done.whenComplete(() { --_clientsConnected; - if (!_acceptNewConnections && _clientsConnected == 0) { - _acceptNewConnections = true; - } }); }); } From af9763f51cdb2ec3ccca5669e7d63b775322151b Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 11:32:10 -0400 Subject: [PATCH 19/33] created proxy_service.dart and consolidated all duplicated code --- .../src/services/chrome_proxy_service.dart | 324 ++-------------- dwds/lib/src/services/proxy_service.dart | 356 ++++++++++++++++++ .../services/web_socket_proxy_service.dart | 145 ++----- 3 files changed, 416 insertions(+), 409 deletions(-) create mode 100644 dwds/lib/src/services/proxy_service.dart diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 4c1f54497..0d1abefec 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -25,29 +25,16 @@ import 'package:dwds/src/services/batched_expression_evaluator.dart'; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; import 'package:dwds/src/services/expression_evaluator.dart'; +import 'package:dwds/src/services/proxy_service.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:logging/logging.dart' hide LogRecord; -import 'package:pub_semver/pub_semver.dart' as semver; import 'package:vm_service/vm_service.dart' hide vmServiceVersion; import 'package:vm_service_interface/vm_service_interface.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; /// A proxy from the chrome debug protocol to the dart vm service protocol. -class ChromeProxyService implements VmServiceInterface { - /// Cache of all existing StreamControllers. - /// - /// These are all created through [onEvent]. - final _streamControllers = >{}; - - /// The root `VM` instance. There can only be one of these, but its isolates - /// are dynamic and roughly map to chrome tabs. - final VM _vm; - - /// Signals when isolate is initialized. - Future get isInitialized => _initializedCompleter.future; - Completer _initializedCompleter = Completer(); - +class ChromeProxyService extends ProxyService { /// Signals when isolate starts. Future get isStarted => _startedCompleter.future; Completer _startedCompleter = Completer(); @@ -93,34 +80,6 @@ class ChromeProxyService implements VmServiceInterface { StreamSubscription? _consoleSubscription; - /// The flags that can be set at runtime via [setFlag] and their respective - /// values. - final Map _currentVmServiceFlags = { - _pauseIsolatesOnStartFlag: false, - }; - - /// The value of the [_pauseIsolatesOnStartFlag]. - /// - /// This value can be updated at runtime via [setFlag]. - bool get pauseIsolatesOnStart => - _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false; - - /// Whether or not the connected app has a pending restart. - bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; - - final _resumeAfterRestartEventsController = - StreamController.broadcast(); - - /// A global stream of resume events for hot restart. - /// - /// The values in the stream are the isolates IDs for the resume event. - /// - /// IMPORTANT: This should only be listened to during a hot-restart or page - /// refresh. The debugger ignores any resume events as long as there is a - /// subscriber to this stream. - Stream get resumeAfterRestartEventsStream => - _resumeAfterRestartEventsController.stream; - /// If non-null, a resume event should await the result of this after resuming /// execution. /// @@ -135,7 +94,7 @@ class ChromeProxyService implements VmServiceInterface { bool terminatingIsolates = false; ChromeProxyService._( - this._vm, + VM vm, this.root, this._assetReader, this.remoteDebugger, @@ -144,10 +103,10 @@ class ChromeProxyService implements VmServiceInterface { this._skipLists, this.executionContext, this._compiler, - ) { + ) : super(vm) { final debugger = Debugger.create( remoteDebugger, - _streamNotify, + streamNotify, _locations, _skipLists, root, @@ -368,9 +327,9 @@ class ChromeProxyService implements VmServiceInterface { // Listen for `registerExtension` and `postEvent` calls. _setUpChromeConsoleListeners(isolateRef); - _vm.isolates?.add(isolateRef); + vm.isolates?.add(isolateRef); - _streamNotify( + streamNotify( 'Isolate', Event( kind: EventKind.kIsolateStart, @@ -378,7 +337,7 @@ class ChromeProxyService implements VmServiceInterface { isolate: isolateRef, ), ); - _streamNotify( + streamNotify( 'Isolate', Event( kind: EventKind.kIsolateRunnable, @@ -391,7 +350,7 @@ class ChromeProxyService implements VmServiceInterface { // isolate, but devtools doesn't recognize extensions after a page refresh // otherwise. for (final extensionRpc in await inspector.getExtensionRpcs()) { - _streamNotify( + streamNotify( 'Isolate', Event( kind: EventKind.kServiceExtensionAdded, @@ -405,7 +364,7 @@ class ChromeProxyService implements VmServiceInterface { // kPausePostRequest event to notify client that the app is paused so that // it can resume: if (hasPendingRestart) { - _streamNotify( + streamNotify( 'Debug', Event( kind: EventKind.kPausePostRequest, @@ -416,7 +375,7 @@ class ChromeProxyService implements VmServiceInterface { } // The service is considered initialized when the first isolate is created. - if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); + if (!initializedCompleter.isCompleted) initializedCompleter.complete(); } /// Should be called when there is a hot restart or full page refresh. @@ -429,10 +388,10 @@ class ChromeProxyService implements VmServiceInterface { final isolate = inspector.isolate; final isolateRef = inspector.isolateRef; - _initializedCompleter = Completer(); + initializedCompleter = Completer(); _startedCompleter = Completer(); _compilerCompleter = Completer(); - _streamNotify( + streamNotify( 'Isolate', Event( kind: EventKind.kIsolateExit, @@ -440,7 +399,7 @@ class ChromeProxyService implements VmServiceInterface { isolate: isolateRef, ), ); - _vm.isolates?.removeWhere((ref) => ref.id == isolate.id); + vm.isolates?.removeWhere((ref) => ref.id == isolate.id); _inspector = null; _expressionEvaluator?.close(); _consoleSubscription?.cancel(); @@ -485,7 +444,7 @@ class ChromeProxyService implements VmServiceInterface { @override Future addBreakpointAtEntry(String isolateId, String functionId) { - return _rpcNotSupportedFuture('addBreakpointAtEntry'); + return rpcNotSupportedFuture('addBreakpointAtEntry'); } @override @@ -587,11 +546,6 @@ class ChromeProxyService implements VmServiceInterface { } } - @override - Future clearVMTimeline() { - return _rpcNotSupportedFuture('clearVMTimeline'); - } - Future _getEvaluationResult( String isolateId, Future Function() evaluation, @@ -804,46 +758,6 @@ class ChromeProxyService implements VmServiceInterface { }, (result) => DwdsEvent.evaluateInFrame(expression, result)); } - @override - Future getAllocationProfile( - String isolateId, { - bool? gc, - bool? reset, - }) { - return _rpcNotSupportedFuture('getAllocationProfile'); - } - - @override - Future getClassList(String isolateId) { - // See dart-lang/webdev/issues/971. - return _rpcNotSupportedFuture('getClassList'); - } - - @override - Future getFlagList() { - return wrapInErrorHandlerAsync('getFlagList', _getFlagList); - } - - Future _getFlagList() { - final flags = _currentVmServiceFlags.entries.map( - (entry) => Flag(name: entry.key, valueAsString: '${entry.value}'), - ); - - return Future.value(FlagList(flags: flags.toList())); - } - - @override - Future getInstances( - String isolateId, - String classId, - int limit, { - bool? includeImplementers, - bool? includeSubclasses, - String? idZoneId, - }) { - return _rpcNotSupportedFuture('getInstances'); - } - @override Future getIsolate(String isolateId) => wrapInErrorHandlerAsync('getIsolate', () => _getIsolate(isolateId)); @@ -992,32 +906,10 @@ class ChromeProxyService implements VmServiceInterface { Future _getVM() { return captureElapsedTime(() async { await isInitialized; - return _vm; + return vm; }, (result) => DwdsEvent.getVM()); } - @override - Future getVMTimeline({ - int? timeOriginMicros, - int? timeExtentMicros, - }) { - return _rpcNotSupportedFuture('getVMTimeline'); - } - - @override - Future getVMTimelineFlags() { - return _rpcNotSupportedFuture('getVMTimelineFlags'); - } - - @override - Future getVersion() => - wrapInErrorHandlerAsync('getVersion', _getVersion); - - Future _getVersion() async { - final version = semver.Version.parse(vmServiceVersion); - return Version(major: version.major, minor: version.minor); - } - @override Future invoke( String isolateId, @@ -1048,14 +940,9 @@ class ChromeProxyService implements VmServiceInterface { return _instanceRef(remote); } - @override - Future kill(String isolateId) { - return _rpcNotSupportedFuture('kill'); - } - @override Stream onEvent(String streamId) { - return _streamControllers.putIfAbsent(streamId, () { + return streamControllers.putIfAbsent(streamId, () { switch (streamId) { case EventStreams.kExtension: return StreamController.broadcast(); @@ -1143,7 +1030,7 @@ class ChromeProxyService implements VmServiceInterface { @override Future registerService(String service, String alias) { - return _rpcNotSupportedFuture('registerService'); + return rpcNotSupportedFuture('registerService'); } @override @@ -1226,7 +1113,7 @@ class ChromeProxyService implements VmServiceInterface { // This lets the client know that we're ready for breakpoint management // and a resume. - _streamNotify( + streamNotify( 'Debug', Event( kind: EventKind.kPausePostRequest, @@ -1266,8 +1153,8 @@ class ChromeProxyService implements VmServiceInterface { }) async { // If there is a subscriber listening for a resume event after hot-restart, // then add the event to the stream and skip processing it. - if (_resumeAfterRestartEventsController.hasListener) { - _resumeAfterRestartEventsController.add(isolateId); + if (resumeAfterRestartEventsController.hasListener) { + resumeAfterRestartEventsController.add(isolateId); return Success(); } @@ -1331,12 +1218,12 @@ class ChromeProxyService implements VmServiceInterface { wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value)); Future _setFlag(String name, String value) async { - if (!_currentVmServiceFlags.containsKey(name)) { - return _rpcNotSupportedFuture('setFlag'); + if (!currentVmServiceFlags.containsKey(name)) { + return rpcNotSupportedFuture('setFlag'); } assert(value == 'true' || value == 'false'); - _currentVmServiceFlags[name] = value == 'true'; + currentVmServiceFlags[name] = value == 'true'; return Success(); } @@ -1347,7 +1234,7 @@ class ChromeProxyService implements VmServiceInterface { String libraryId, bool isDebuggable, ) { - return _rpcNotSupportedFuture('setLibraryDebuggable'); + return rpcNotSupportedFuture('setLibraryDebuggable'); } @override @@ -1366,31 +1253,19 @@ class ChromeProxyService implements VmServiceInterface { wrapInErrorHandlerAsync('setVMName', () => _setVMName(name)); Future _setVMName(String name) async { - _vm.name = name; - _streamNotify( + vm.name = name; + streamNotify( 'VM', Event( kind: EventKind.kVMUpdate, timestamp: DateTime.now().millisecondsSinceEpoch, // We are not guaranteed to have an isolate at this point in time. isolate: null, - )..vm = toVMRef(_vm), + )..vm = toVMRef(vm), ); return Success(); } - @override - Future setVMTimelineFlags(List recordedStreams) { - return _rpcNotSupportedFuture('setVMTimelineFlags'); - } - - @override - Future streamCancel(String streamId) { - // TODO: We should implement this (as we've already implemented - // streamListen). - return _rpcNotSupportedFuture('streamCancel'); - } - @override Future streamListen(String streamId) => wrapInErrorHandlerAsync('streamListen', () => _streamListen(streamId)); @@ -1402,20 +1277,6 @@ class ChromeProxyService implements VmServiceInterface { return Success(); } - @override - Future clearCpuSamples(String isolateId) { - return _rpcNotSupportedFuture('clearCpuSamples'); - } - - @override - Future getCpuSamples( - String isolateId, - int timeOriginMicros, - int timeExtentMicros, - ) { - return _rpcNotSupportedFuture('getCpuSamples'); - } - /// Returns a streamController that listens for console logs from chrome and /// adds all events passing [filter] to the stream. StreamController _chromeConsoleStreamController( @@ -1488,12 +1349,13 @@ class ChromeProxyService implements VmServiceInterface { /// Parses the [DebugEvent] and emits a corresponding Dart VM Service /// protocol [Event]. + @override void parseDebugEvent(DebugEvent debugEvent) { if (terminatingIsolates) return; if (!_isIsolateRunning) return; final isolateRef = inspector.isolateRef; - _streamNotify( + streamNotify( EventStreams.kExtension, Event( kind: EventKind.kExtension, @@ -1509,6 +1371,7 @@ class ChromeProxyService implements VmServiceInterface { /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service /// protocol [Event]. + @override void parseRegisterEvent(RegisterEvent registerEvent) { if (terminatingIsolates) return; if (!_isIsolateRunning) return; @@ -1518,7 +1381,7 @@ class ChromeProxyService implements VmServiceInterface { final service = registerEvent.eventData; isolate.extensionRPCs?.add(service); - _streamNotify( + streamNotify( EventStreams.kIsolate, Event( kind: EventKind.kServiceExtensionAdded, @@ -1548,7 +1411,7 @@ class ChromeProxyService implements VmServiceInterface { if (event.args[1].type != 'object') break; final inspectee = await _instanceRef(event.args[1]); - _streamNotify( + streamNotify( EventStreams.kDebug, Event( kind: EventKind.kInspect, @@ -1574,14 +1437,8 @@ class ChromeProxyService implements VmServiceInterface { }); } - void _streamNotify(String streamId, Event event) { - final controller = _streamControllers[streamId]; - if (controller == null) return; - controller.add(event); - } - Future _firstStreamEvent(String streamId, String eventKind) { - final controller = _streamControllers[streamId]!; + final controller = streamControllers[streamId]!; return controller.stream.firstWhere((event) => event.kind == eventKind); } @@ -1616,7 +1473,7 @@ class ChromeProxyService implements VmServiceInterface { zone: await _instanceRef(logParams['zone']), ); - _streamNotify( + streamNotify( EventStreams.kLogging, Event( kind: EventKind.kLogging, @@ -1659,11 +1516,6 @@ class ChromeProxyService implements VmServiceInterface { return logParams; } - @override - Future getVMTimelineMicros() { - return _rpcNotSupportedFuture('getVMTimelineMicros'); - } - @override Future yieldControlToDDS(String uri) async { final canYield = DebugService.yieldControlToDDS(uri); @@ -1677,115 +1529,11 @@ class ChromeProxyService implements VmServiceInterface { } } - @override - Future getInboundReferences( - String isolateId, - String targetId, - int limit, { - String? idZoneId, - }) { - return _rpcNotSupportedFuture('getInboundReferences'); - } - - @override - Future getRetainingPath( - String isolateId, - String targetId, - int limit, { - String? idZoneId, - }) { - return _rpcNotSupportedFuture('getRetainingPath'); - } - - @override - Future requestHeapSnapshot(String isolateId) { - return _rpcNotSupportedFuture('requestHeapSnapshot'); - } - - @override - Future getIsolateGroup(String isolateGroupId) { - return _rpcNotSupportedFuture('getIsolateGroup'); - } - - @override - Future getIsolateGroupMemoryUsage(String isolateGroupId) { - return _rpcNotSupportedFuture('getIsolateGroupMemoryUsage'); - } - - @override - Future getSupportedProtocols() => - wrapInErrorHandlerAsync('getSupportedProtocols', _getSupportedProtocols); - - Future _getSupportedProtocols() async { - final version = semver.Version.parse(vmServiceVersion); - return ProtocolList( - protocols: [ - Protocol( - protocolName: 'VM Service', - major: version.major, - minor: version.minor, - ), - ], - ); - } - Future _instanceRef(RemoteObject? obj) async { final instance = obj == null ? null : await inspector.instanceRefFor(obj); return instance ?? InstanceHelper.kNullInstanceRef; } - static RPCError _rpcNotSupported(String method) { - return RPCError( - method, - RPCErrorKind.kMethodNotFound.code, - '$method: Not supported on web devices', - ); - } - - static Future _rpcNotSupportedFuture(String method) { - return Future.error(_rpcNotSupported(method)); - } - - @override - Future getProcessMemoryUsage() => - _rpcNotSupportedFuture('getProcessMemoryUsage'); - - @override - Future getPorts(String isolateId) => throw UnimplementedError(); - - @override - Future getAllocationTraces( - String isolateId, { - int? timeOriginMicros, - int? timeExtentMicros, - String? classId, - }) => throw UnimplementedError(); - - @override - Future setTraceClassAllocation( - String isolateId, - String classId, - bool enable, - ) => throw UnimplementedError(); - - @override - Future setBreakpointState( - String isolateId, - String breakpointId, - bool enable, - ) => throw UnimplementedError(); - - @override - Future streamCpuSamplesWithUserTag(List userTags) => - _rpcNotSupportedFuture('streamCpuSamplesWithUserTag'); - - /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service - /// are unimplemented in DWDS. - @override - dynamic noSuchMethod(Invocation invocation) { - return super.noSuchMethod(invocation); - } - /// Validate that isolateId matches the current isolate we're connected to and /// return that isolate. /// @@ -1839,5 +1587,3 @@ const _stderrTypes = ['error']; /// The `type`s of [ConsoleAPIEvent]s that are treated as `stdout` logs. const _stdoutTypes = ['log', 'info', 'warning']; - -const _pauseIsolatesOnStartFlag = 'pause_isolates_on_start'; diff --git a/dwds/lib/src/services/proxy_service.dart b/dwds/lib/src/services/proxy_service.dart new file mode 100644 index 000000000..93f17656a --- /dev/null +++ b/dwds/lib/src/services/proxy_service.dart @@ -0,0 +1,356 @@ +// Copyright (c) 2025, 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. + +import 'dart:async'; + +import 'package:dwds/data/debug_event.dart'; +import 'package:dwds/data/register_event.dart'; +import 'package:dwds/src/events.dart'; +import 'package:dwds/src/utilities/shared.dart'; +import 'package:pub_semver/pub_semver.dart' as semver; +import 'package:vm_service/vm_service.dart' as vm_service; +import 'package:vm_service_interface/vm_service_interface.dart'; + +const pauseIsolatesOnStartFlag = 'pause_isolates_on_start'; + +/// Abstract base class for VM service proxy implementations. +abstract class ProxyService implements VmServiceInterface { + /// Cache of all existing StreamControllers. + /// + /// These are all created through [onEvent]. + final Map> _streamControllers = {}; + + /// The root `VM` instance. + final vm_service.VM _vm; + + /// Signals when isolate is initialized. + Future get isInitialized => _initializedCompleter.future; + Completer _initializedCompleter = Completer(); + + /// The flags that can be set at runtime via [setFlag] and their respective + /// values. + final Map _currentVmServiceFlags = { + pauseIsolatesOnStartFlag: false, + }; + + /// The value of the [pauseIsolatesOnStartFlag]. + /// + /// This value can be updated at runtime via [setFlag]. + bool get pauseIsolatesOnStart => + _currentVmServiceFlags[pauseIsolatesOnStartFlag] ?? false; + + /// Stream controller for resume events after restart. + final _resumeAfterRestartEventsController = + StreamController.broadcast(); + + /// A global stream of resume events for hot restart. + /// + /// The values in the stream are the isolates IDs for the resume event. + /// + /// IMPORTANT: This should only be listened to during a hot-restart or page + /// refresh. The debugger ignores any resume events as long as there is a + /// subscriber to this stream. + Stream get resumeAfterRestartEventsStream => + _resumeAfterRestartEventsController.stream; + + /// Whether or not the connected app has a pending restart. + bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; + + // Protected accessors for subclasses + vm_service.VM get vm => _vm; + Map> get streamControllers => + _streamControllers; + StreamController get resumeAfterRestartEventsController => + _resumeAfterRestartEventsController; + Map get currentVmServiceFlags => _currentVmServiceFlags; + Completer get initializedCompleter => _initializedCompleter; + set initializedCompleter(Completer completer) => + _initializedCompleter = completer; + + ProxyService(this._vm); + + /// Sends events to stream controllers. + void streamNotify(String streamId, vm_service.Event event) { + final controller = _streamControllers[streamId]; + if (controller == null) return; + controller.add(event); + } + + /// Returns a broadcast stream for the given streamId. + @override + Stream onEvent(String streamId) { + return _streamControllers.putIfAbsent(streamId, () { + return StreamController.broadcast(); + }).stream; + } + + @override + Future streamListen(String streamId) => + wrapInErrorHandlerAsync('streamListen', () => _streamListen(streamId)); + + Future _streamListen(String streamId) async { + onEvent(streamId); + return vm_service.Success(); + } + + @override + Future streamCancel(String streamId) { + // TODO: We should implement this (as we've already implemented + // streamListen). + return _rpcNotSupportedFuture('streamCancel'); + } + + @override + Future getVM() => wrapInErrorHandlerAsync('getVM', _getVM); + + Future _getVM() { + return captureElapsedTime(() async { + return _vm; + }, (result) => DwdsEvent.getVM()); + } + + @override + Future getFlagList() => + wrapInErrorHandlerAsync('getFlagList', _getFlagList); + + Future _getFlagList() async { + final flags = _currentVmServiceFlags.entries.map( + (entry) => + vm_service.Flag(name: entry.key, valueAsString: '${entry.value}'), + ); + return vm_service.FlagList(flags: flags.toList()); + } + + @override + Future setFlag(String name, String value) => + wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value)); + + Future _setFlag(String name, String value) async { + if (!_currentVmServiceFlags.containsKey(name)) { + throw vm_service.RPCError( + 'setFlag', + vm_service.RPCErrorKind.kInvalidRequest.code, + 'Cannot set flag "$name" (invalid flag)', + ); + } + + assert(value == 'true' || value == 'false'); + _currentVmServiceFlags[name] = value == 'true'; + return vm_service.Success(); + } + + @override + Future getSupportedProtocols() => + wrapInErrorHandlerAsync('getSupportedProtocols', _getSupportedProtocols); + + Future _getSupportedProtocols() async { + final version = semver.Version.parse(vm_service.vmServiceVersion); + return vm_service.ProtocolList( + protocols: [ + vm_service.Protocol( + protocolName: 'VM Service', + major: version.major, + minor: version.minor, + ), + ], + ); + } + + @override + Future getVersion() => + wrapInErrorHandlerAsync('getVersion', _getVersion); + + Future _getVersion() async { + final version = semver.Version.parse(vm_service.vmServiceVersion); + return vm_service.Version(major: version.major, minor: version.minor); + } + + /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service + /// protocol [Event]s. + void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) { + for (final debugEvent in debugEvents.events) { + parseDebugEvent(debugEvent); + } + } + + /// Parses the [DebugEvent] and emits a corresponding Dart VM Service + /// protocol [Event]. + void parseDebugEvent(DebugEvent debugEvent); + + /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service + /// protocol [Event]. + void parseRegisterEvent(RegisterEvent registerEvent); + + /// Standard RPC error for unsupported methods. + static vm_service.RPCError _rpcNotSupported(String method) { + return vm_service.RPCError( + method, + vm_service.RPCErrorKind.kMethodNotFound.code, + '$method: Not supported on web devices', + ); + } + + /// Standard future error for unsupported methods. + static Future _rpcNotSupportedFuture(String method) { + return Future.error(_rpcNotSupported(method)); + } + + /// Protected accessor for _rpcNotSupportedFuture for subclasses + Future rpcNotSupportedFuture(String method) { + return _rpcNotSupportedFuture(method); + } + + // Default implementations for unsupported methods + @override + Future getAllocationProfile( + String isolateId, { + bool? gc, + bool? reset, + }) { + return _rpcNotSupportedFuture('getAllocationProfile'); + } + + @override + Future getClassList(String isolateId) { + return _rpcNotSupportedFuture('getClassList'); + } + + @override + Future getInstances( + String isolateId, + String classId, + int limit, { + bool? includeImplementers, + bool? includeSubclasses, + String? idZoneId, + }) { + return _rpcNotSupportedFuture('getInstances'); + } + + @override + Future kill(String isolateId) { + return _rpcNotSupportedFuture('kill'); + } + + @override + Future clearVMTimeline() { + return _rpcNotSupportedFuture('clearVMTimeline'); + } + + @override + Future getVMTimeline({ + int? timeOriginMicros, + int? timeExtentMicros, + }) { + return _rpcNotSupportedFuture('getVMTimeline'); + } + + @override + Future getVMTimelineFlags() { + return _rpcNotSupportedFuture('getVMTimelineFlags'); + } + + @override + Future setVMTimelineFlags(List recordedStreams) { + return _rpcNotSupportedFuture('setVMTimelineFlags'); + } + + @override + Future getVMTimelineMicros() { + return _rpcNotSupportedFuture('getVMTimelineMicros'); + } + + @override + Future getInboundReferences( + String isolateId, + String targetId, + int limit, { + String? idZoneId, + }) { + return _rpcNotSupportedFuture('getInboundReferences'); + } + + @override + Future getRetainingPath( + String isolateId, + String targetId, + int limit, { + String? idZoneId, + }) { + return _rpcNotSupportedFuture('getRetainingPath'); + } + + @override + Future requestHeapSnapshot(String isolateId) { + return _rpcNotSupportedFuture('requestHeapSnapshot'); + } + + @override + Future getIsolateGroup(String isolateGroupId) { + return _rpcNotSupportedFuture('getIsolateGroup'); + } + + @override + Future getIsolateGroupMemoryUsage( + String isolateGroupId, + ) { + return _rpcNotSupportedFuture('getIsolateGroupMemoryUsage'); + } + + @override + Future getProcessMemoryUsage() => + _rpcNotSupportedFuture('getProcessMemoryUsage'); + + @override + Future getPorts(String isolateId) => + throw UnimplementedError(); + + @override + Future getAllocationTraces( + String isolateId, { + int? timeOriginMicros, + int? timeExtentMicros, + String? classId, + }) => throw UnimplementedError(); + + @override + Future setTraceClassAllocation( + String isolateId, + String classId, + bool enable, + ) => throw UnimplementedError(); + + @override + Future setBreakpointState( + String isolateId, + String breakpointId, + bool enable, + ) => throw UnimplementedError(); + + @override + Future streamCpuSamplesWithUserTag( + List userTags, + ) => _rpcNotSupportedFuture('streamCpuSamplesWithUserTag'); + + @override + Future getCpuSamples( + String isolateId, + int timeOriginMicros, + int timeExtentMicros, + ) { + return _rpcNotSupportedFuture('getCpuSamples'); + } + + @override + Future clearCpuSamples(String isolateId) { + return _rpcNotSupportedFuture('clearCpuSamples'); + } + + /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service + /// are unimplemented in DWDS. + @override + dynamic noSuchMethod(Invocation invocation) { + return super.noSuchMethod(invocation); + } +} diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index 394df02e5..b66fffe6c 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -13,13 +13,12 @@ import 'package:dwds/data/service_extension_request.dart'; import 'package:dwds/data/service_extension_response.dart'; import 'package:dwds/src/connections/app_connection.dart'; import 'package:dwds/src/events.dart'; +import 'package:dwds/src/services/proxy_service.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:logging/logging.dart'; -import 'package:pub_semver/pub_semver.dart' as semver; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart'; -import 'package:vm_service_interface/vm_service_interface.dart'; /// Defines callbacks for sending messages to the connected client. /// Returns the number of clients the request was successfully sent to. @@ -84,13 +83,9 @@ class _ServiceExtensionTracker { } /// WebSocket-based VM service proxy for web debugging. -class WebSocketProxyService implements VmServiceInterface { +class WebSocketProxyService extends ProxyService { final _logger = Logger('WebSocketProxyService'); - /// Signals when the isolate is ready. - Future get isInitialized => _initializedCompleter.future; - Completer _initializedCompleter = Completer(); - /// Active service extension trackers by request ID. final Map _pendingServiceExtensionTrackers = {}; @@ -111,42 +106,16 @@ class WebSocketProxyService implements VmServiceInterface { /// Active connection count for this service. int _activeConnectionCount = 0; - /// Event stream controllers. - final Map> _streamControllers = {}; - - /// VM service runtime flags. - final Map _currentVmServiceFlags = { - _pauseIsolatesOnStartFlag: false, - }; - - /// Stream controller for resume events after restart. - final _resumeAfterRestartEventsController = - StreamController.broadcast(); - - /// Stream of resume events after restart. - Stream get resumeAfterRestartEventsStream => - _resumeAfterRestartEventsController.stream; - - /// Whether there's a pending restart. - bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener; - - /// Whether isolates should pause on start. - bool get pauseIsolatesOnStart => - _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false; - /// Counter for generating unique isolate IDs across page refreshes static int _globalIsolateIdCounter = 0; bool get _isIsolateRunning => _isolateRunning; - /// Root VM instance. - final vm_service.VM _vm; - WebSocketProxyService._( this.sendClientRequest, - this._vm, + vm_service.VM vm, this.appConnection, - ); // Isolate state + ) : super(vm); // Isolate state vm_service.IsolateRef? _isolateRef; bool _isolateRunning = false; vm_service.Event? _currentPauseEvent; @@ -203,7 +172,7 @@ class WebSocketProxyService implements VmServiceInterface { _isolateRef = isolateRef; _isolateRunning = true; - _vm.isolates?.add(isolateRef); + vm.isolates?.add(isolateRef); final timestamp = DateTime.now().millisecondsSinceEpoch; _logger.fine( @@ -240,7 +209,7 @@ class WebSocketProxyService implements VmServiceInterface { } // Complete initialization after isolate is set up - if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); + if (!initializedCompleter.isCompleted) initializedCompleter.complete(); } /// Handles a connection being closed. @@ -325,7 +294,7 @@ class WebSocketProxyService implements VmServiceInterface { ); } - _vm.isolates?.removeWhere((ref) => ref.id == isolateRef?.id); + vm.isolates?.removeWhere((ref) => ref.id == isolateRef?.id); // Reset state _isolateRef = null; @@ -333,14 +302,14 @@ class WebSocketProxyService implements VmServiceInterface { _currentPauseEvent = null; _mainHasStarted = false; - if (_initializedCompleter.isCompleted) { - _initializedCompleter = Completer(); + if (initializedCompleter.isCompleted) { + initializedCompleter = Completer(); } } /// Sends events to stream controllers. void _streamNotify(String streamId, vm_service.Event event) { - final controller = _streamControllers[streamId]; + final controller = streamControllers[streamId]; if (controller == null) return; controller.add(event); } @@ -351,7 +320,7 @@ class WebSocketProxyService implements VmServiceInterface { String libraryId, bool isDebuggable, ) { - return _rpcNotSupportedFuture('setLibraryDebuggable'); + return rpcNotSupportedFuture('setLibraryDebuggable'); } @override @@ -364,18 +333,6 @@ class WebSocketProxyService implements VmServiceInterface { return Success(); } - static Future _rpcNotSupportedFuture(String method) { - return Future.error(_rpcNotSupported(method)); - } - - static RPCError _rpcNotSupported(String method) { - return RPCError( - method, - RPCErrorKind.kMethodNotFound.code, - '$method: Not supported on web devices', - ); - } - @override Future getIsolate(String isolateId) => wrapInErrorHandlerAsync('getIsolate', () => _getIsolate(isolateId)); @@ -407,45 +364,9 @@ class WebSocketProxyService implements VmServiceInterface { ); } - /// Returns a broadcast stream for the given streamId. - @override - Stream onEvent(String streamId) { - return _streamControllers.putIfAbsent(streamId, () { - switch (streamId) { - case vm_service.EventStreams.kExtension: - case vm_service.EventStreams.kIsolate: - case vm_service.EventStreams.kVM: - case vm_service.EventStreams.kGC: - case vm_service.EventStreams.kTimeline: - case vm_service.EventStreams.kService: - case vm_service.EventStreams.kDebug: - case vm_service.EventStreams.kLogging: - case vm_service.EventStreams.kStdout: - case vm_service.EventStreams.kStderr: - return StreamController.broadcast(); - default: - _logger.warning('Unsupported stream: $streamId'); - throw vm_service.RPCError( - 'streamListen', - vm_service.RPCErrorKind.kInvalidParams.code, - 'Stream `$streamId` not supported on web devices', - ); - } - }).stream; - } - - @override - Future streamListen(String streamId) => - wrapInErrorHandlerAsync('streamListen', () => _streamListen(streamId)); - - Future _streamListen(String streamId) async { - onEvent(streamId); - return vm_service.Success(); - } - /// Adds events to stream controllers. void addEvent(String streamId, vm_service.Event event) { - final controller = _streamControllers[streamId]; + final controller = streamControllers[streamId]; if (controller != null && !controller.isClosed) { controller.add(event); } else { @@ -490,16 +411,16 @@ class WebSocketProxyService implements VmServiceInterface { if (_isIsolateRunning && _isolateRef != null) { // Make sure our isolate is in the VM's isolate list final isolateExists = - _vm.isolates?.any((ref) => ref.id == _isolateRef!.id) ?? false; + vm.isolates?.any((ref) => ref.id == _isolateRef!.id) ?? false; if (!isolateExists) { - _vm.isolates?.add(_isolateRef!); + vm.isolates?.add(_isolateRef!); } } else { // If no isolate is running, make sure the list is empty - _vm.isolates?.clear(); + vm.isolates?.clear(); } - return _vm; + return vm; }, (result) => DwdsEvent.getVM()); } @@ -511,21 +432,6 @@ class WebSocketProxyService implements VmServiceInterface { ); } - /// Returns supported VM service protocols. - @override - Future getSupportedProtocols() async { - final version = semver.Version.parse(vm_service.vmServiceVersion); - return vm_service.ProtocolList( - protocols: [ - vm_service.Protocol( - protocolName: 'VM Service', - major: version.major, - minor: version.minor, - ), - ], - ); - } - @override Future reloadSources( String isolateId, { @@ -760,6 +666,7 @@ class WebSocketProxyService implements VmServiceInterface { /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service /// protocol [Event]. + @override void parseRegisterEvent(RegisterEvent registerEvent) { if (!_isIsolateRunning || _isolateRef == null) { _logger.warning('Cannot register service extension - no isolate running'); @@ -789,6 +696,7 @@ class WebSocketProxyService implements VmServiceInterface { /// Parses the [DebugEvent] and emits a corresponding Dart VM Service /// protocol [Event]. + @override void parseDebugEvent(DebugEvent debugEvent) { if (!_isIsolateRunning || _isolateRef == null) { _logger.warning('Cannot parse debug event - no isolate running'); @@ -814,13 +722,13 @@ class WebSocketProxyService implements VmServiceInterface { wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value)); Future _setFlag(String name, String value) async { - if (!_currentVmServiceFlags.containsKey(name)) { - return _rpcNotSupportedFuture('setFlag'); + if (!currentVmServiceFlags.containsKey(name)) { + return rpcNotSupportedFuture('setFlag'); } assert(value == 'true' || value == 'false'); - final oldValue = _currentVmServiceFlags[name]; - _currentVmServiceFlags[name] = value == 'true'; + final oldValue = currentVmServiceFlags[name]; + currentVmServiceFlags[name] = value == 'true'; // Handle pause_isolates_on_start flag changes if (name == _pauseIsolatesOnStartFlag && @@ -894,8 +802,8 @@ class WebSocketProxyService implements VmServiceInterface { String? step, int? frameIndex, }) async { - if (hasPendingRestart && !_resumeAfterRestartEventsController.isClosed) { - _resumeAfterRestartEventsController.add(isolateId); + if (hasPendingRestart && !resumeAfterRestartEventsController.isClosed) { + resumeAfterRestartEventsController.add(isolateId); } else { if (!_mainHasStarted) { try { @@ -942,7 +850,7 @@ class WebSocketProxyService implements VmServiceInterface { @override Future registerService(String service, String alias) { - return _rpcNotSupportedFuture('registerService'); + return rpcNotSupportedFuture('registerService'); } @override @@ -999,9 +907,6 @@ class WebSocketProxyService implements VmServiceInterface { awaiterFrames: [], ); } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } /// Extended ReloadReport that includes additional metadata in JSON output. From a61bde1ebbd0f17783b961d41c2ffeb8822f323f Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 13:31:42 -0400 Subject: [PATCH 20/33] consolidate webSocketAppDebugService and AppDebugServices --- .../lib/src/connections/debug_connection.dart | 22 ++- dwds/lib/src/handlers/dev_handler.dart | 181 ++++++++++-------- dwds/lib/src/services/app_debug_services.dart | 50 ++++- .../web_socket_app_debug_services.dart | 48 ----- 4 files changed, 163 insertions(+), 138 deletions(-) delete mode 100644 dwds/lib/src/services/web_socket_app_debug_services.dart diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart index c15b8c79e..2d37cd5d0 100644 --- a/dwds/lib/src/connections/debug_connection.dart +++ b/dwds/lib/src/connections/debug_connection.dart @@ -22,9 +22,11 @@ class DebugConnection { Future? _closed; DebugConnection(this._appDebugServices) { - _appDebugServices.chromeProxyService?.remoteDebugger.onClose.first.then( - (_) => close(), - ); + // Only setup Chrome-specific close handling if we have a ChromeProxyService + final proxyService = _appDebugServices.proxyService; + if (proxyService is ChromeProxyService) { + proxyService.remoteDebugger.onClose.first.then((_) => close()); + } } /// The port of the host Dart VM Service. @@ -41,7 +43,10 @@ class DebugConnection { Future close() => _closed ??= () async { - await _appDebugServices.chromeProxyService?.remoteDebugger.close(); + final proxyService = _appDebugServices.proxyService; + if (proxyService is ChromeProxyService) { + await proxyService.remoteDebugger.close(); + } await _appDebugServices.close(); _onDoneCompleter.complete(); }(); @@ -50,5 +55,10 @@ class DebugConnection { } /// [ChromeProxyService] of a [DebugConnection] for internal use only. -ChromeProxyService fetchChromeProxyService(DebugConnection debugConnection) => - debugConnection._appDebugServices.chromeProxyService; +ChromeProxyService fetchChromeProxyService(DebugConnection debugConnection) { + final service = debugConnection._appDebugServices.proxyService; + if (service is ChromeProxyService) { + return service; + } + throw StateError('ChromeProxyService not available in this debug connection'); +} diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 07ccf1a17..b15233152 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -31,9 +31,10 @@ import 'package:dwds/src/servers/devtools.dart'; import 'package:dwds/src/servers/extension_backend.dart'; import 'package:dwds/src/servers/extension_debugger.dart'; import 'package:dwds/src/services/app_debug_services.dart'; +import 'package:dwds/src/services/chrome_proxy_service.dart'; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; -import 'package:dwds/src/services/web_socket_app_debug_services.dart'; +import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:dwds/src/web_socket_dwds_vm_client.dart'; import 'package:logging/logging.dart'; @@ -315,18 +316,19 @@ class DevHandler { IAppDebugServices appServices, AppConnection appConnection, ) { - safeUnawaited( - appServices.chromeProxyService.remoteDebugger.onClose.first.whenComplete( - () async { + final chromeProxy = appServices.proxyService; + if (chromeProxy is ChromeProxyService) { + safeUnawaited( + chromeProxy.remoteDebugger.onClose.first.whenComplete(() async { await appServices.close(); _servicesByAppId.remove(appConnection.request.appId); _logger.info( 'Stopped debug service on ' 'ws://${appServices.debugService.hostname}:${appServices.debugService.port}\n', ); - }, - ), - ); + }), + ); + } } void _handleConnection(SocketConnection injectedConnection) { @@ -420,7 +422,9 @@ class DevHandler { if (message == null) return; final appId = connection.request.appId; - final wsService = _servicesByAppId[appId]?.webSocketProxyService; + final proxyService = _servicesByAppId[appId]?.proxyService; + final wsService = + proxyService is WebSocketProxyService ? proxyService : null; if (wsService == null) { _logger.warning( @@ -452,22 +456,21 @@ class DevHandler { ) async { if (message == null) return; - if (message is HotReloadResponse) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .completeHotReload(message); - } else if (message is IsolateExit) { + final appId = connection.request.appId; + final proxyService = _servicesByAppId[appId]?.proxyService; + final chromeService = + proxyService is ChromeProxyService ? proxyService : null; + + if (message is IsolateExit) { _handleIsolateExit(connection); } else if (message is IsolateStart) { await _handleIsolateStart(connection); } else if (message is BatchedDebugEvents) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseBatchedDebugEvents(message); + chromeService?.parseBatchedDebugEvents(message); } else if (message is DebugEvent) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseDebugEvent(message); + chromeService?.parseDebugEvent(message); } else if (message is RegisterEvent) { - _servicesByAppId[connection.request.appId]?.chromeProxyService - .parseRegisterEvent(message); + chromeService?.parseRegisterEvent(message); } else { throw UnsupportedError( 'Message type ${message.runtimeType} is not supported in Chrome mode', @@ -571,13 +574,16 @@ class DevHandler { debuggerStart: debuggerStart, devToolsStart: DateTime.now(), ); - await _launchDevTools( - appServices.chromeProxyService.remoteDebugger, - _constructDevToolsUri( - appServices.debugService.uri, - ideQueryParam: 'Dwds', - ), - ); + final chromeProxy = appServices.proxyService; + if (chromeProxy is ChromeProxyService) { + await _launchDevTools( + chromeProxy.remoteDebugger, + _constructDevToolsUri( + appServices.debugService.uri, + ideQueryParam: 'Dwds', + ), + ); + } } /// Creates a debug connection for WebSocket mode. @@ -587,9 +593,9 @@ class DevHandler { final appDebugServices = await loadAppServices(appConnection); // Initialize WebSocket proxy service - final webSocketProxyService = appDebugServices.webSocketProxyService; - if (webSocketProxyService != null) { - await webSocketProxyService.isInitialized; + final proxyService = appDebugServices.proxyService; + if (proxyService is WebSocketProxyService) { + await proxyService.isInitialized; _logger.fine('WebSocket proxy service initialized successfully'); } else { _logger.warning('WebSocket proxy service is null'); @@ -606,9 +612,9 @@ class DevHandler { // Initialize Chrome proxy service try { - final chromeProxyService = appDebugServices.chromeProxyService; - if (chromeProxyService != null) { - await chromeProxyService.isInitialized; + final proxyService = appDebugServices.proxyService; + if (proxyService is ChromeProxyService) { + await proxyService.isInitialized; _logger.fine('Chrome proxy service initialized successfully'); } else { _logger.warning('Chrome proxy service is null'); @@ -653,24 +659,30 @@ class DevHandler { // Disconnect any old connection (eg. those in the keep-alive waiting // state when reloading the page). existingConnection?.shutDown(); - services.chromeProxyService.destroyIsolate(); + final chromeProxy = services.proxyService; + if (chromeProxy is ChromeProxyService) { + chromeProxy.destroyIsolate(); + } // Reconnect to existing service. services.connectedInstanceId = message.instanceId; - if (services.chromeProxyService.pauseIsolatesOnStart) { - // If the pause-isolates-on-start flag is set, we need to wait for - // the resume event to run the app's main() method. - _waitForResumeEventToRunMain( - services.chromeProxyService.resumeAfterRestartEventsStream, - readyToRunMainCompleter, - ); - } else { - // Otherwise, we can run the app's main() method immediately. - readyToRunMainCompleter.complete(); - } + final chromeService = services.proxyService; + if (chromeService is ChromeProxyService) { + if (chromeService.pauseIsolatesOnStart) { + // If the pause-isolates-on-start flag is set, we need to wait for + // the resume event to run the app's main() method. + _waitForResumeEventToRunMain( + chromeService.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } else { + // Otherwise, we can run the app's main() method immediately. + readyToRunMainCompleter.complete(); + } - await services.chromeProxyService.createIsolate(connection); + await chromeService.createIsolate(connection); + } } else { // If this is the initial app connection, we can run the app's main() // method immediately. @@ -762,11 +774,16 @@ class DevHandler { _logger.finest('WebSocket service reconnected for app: ${message.appId}'); - _setupMainExecution( - services.webSocketProxyService?.pauseIsolatesOnStart == true, - services.webSocketProxyService?.resumeAfterRestartEventsStream, - readyToRunMainCompleter, - ); + final wsService = services.proxyService; + if (wsService is WebSocketProxyService) { + _setupMainExecution( + wsService.pauseIsolatesOnStart, + wsService.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } else { + readyToRunMainCompleter.complete(); + } await _handleIsolateStart(newConnection); } @@ -809,7 +826,10 @@ class DevHandler { 'Isolate exit handled by WebSocket proxy service for app: $appId', ); } else { - _servicesByAppId[appId]?.chromeProxyService.destroyIsolate(); + final proxyService = _servicesByAppId[appId]?.proxyService; + if (proxyService is ChromeProxyService) { + proxyService.destroyIsolate(); + } } } @@ -818,13 +838,15 @@ class DevHandler { final appId = appConnection.request.appId; if (useWebSocketConnection) { - await _servicesByAppId[appId]?.webSocketProxyService?.createIsolate( - appConnection, - ); + final proxyService = _servicesByAppId[appId]?.proxyService; + if (proxyService is WebSocketProxyService) { + await proxyService.createIsolate(appConnection); + } } else { - await _servicesByAppId[appId]?.chromeProxyService.createIsolate( - appConnection, - ); + final proxyService = _servicesByAppId[appId]?.proxyService; + if (proxyService is ChromeProxyService) { + await proxyService.createIsolate(appConnection); + } } } @@ -871,15 +893,18 @@ class DevHandler { ); final encodedUri = await debugService.encodedUri; _logger.info('Debug service listening on $encodedUri\n'); - await appDebugService.chromeProxyService.remoteDebugger.sendCommand( - 'Runtime.evaluate', - params: { - 'expression': - 'console.log(' - '"This app is linked to the debug service: $encodedUri"' - ');', - }, - ); + final chromeProxy = appDebugService.proxyService; + if (chromeProxy is ChromeProxyService) { + await chromeProxy.remoteDebugger.sendCommand( + 'Runtime.evaluate', + params: { + 'expression': + 'console.log(' + '"This app is linked to the debug service: $encodedUri"' + ');', + }, + ); + } // Notify that DWDS has been launched and a debug connection has been made: _maybeEmitDwdsLaunchEvent(); @@ -989,18 +1014,20 @@ class DevHandler { extensionDebugger.sendEvent('dwds.debugUri', debugService.uri); final encodedUri = await debugService.encodedUri; extensionDebugger.sendEvent('dwds.encodedUri', encodedUri); - safeUnawaited( - appServices.chromeProxyService.remoteDebugger.onClose.first - .whenComplete(() async { - appServices?.chromeProxyService.destroyIsolate(); - await appServices?.close(); - _servicesByAppId.remove(devToolsRequest.appId); - _logger.info( - 'Stopped debug service on ' - '${await appServices?.debugService.encodedUri}\n', - ); - }), - ); + final chromeProxy = appServices.proxyService; + if (chromeProxy is ChromeProxyService) { + safeUnawaited( + chromeProxy.remoteDebugger.onClose.first.whenComplete(() async { + chromeProxy.destroyIsolate(); + await appServices?.close(); + _servicesByAppId.remove(devToolsRequest.appId); + _logger.info( + 'Stopped debug service on ' + '${await appServices?.debugService.encodedUri}\n', + ); + }), + ); + } extensionDebugConnections.add(DebugConnection(appServices)); _servicesByAppId[appId] = appServices; } diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index 77b83086e..1f1986561 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -7,6 +7,8 @@ import 'package:dwds/src/events.dart'; import 'package:dwds/src/services/chrome_proxy_service.dart' show ChromeProxyService; import 'package:dwds/src/services/debug_service.dart'; +import 'package:dwds/src/services/proxy_service.dart'; +import 'package:dwds/src/web_socket_dwds_vm_client.dart'; /// Common interface for debug service containers. abstract class IAppDebugServices { @@ -17,8 +19,7 @@ abstract class IAppDebugServices { String? get connectedInstanceId; set connectedInstanceId(String? id); Future close(); - dynamic get chromeProxyService; - dynamic get webSocketProxyService; + ProxyService get proxyService; } /// Chrome-based debug services container. @@ -56,14 +57,49 @@ class AppDebugServices implements IAppDebugServices { set connectedInstanceId(String? id) => _connectedInstanceId = id; @override - ChromeProxyService get chromeProxyService => + ProxyService get proxyService => debugService.chromeProxyService as ChromeProxyService; - // WebSocket functionality not available in Chrome-based service - @override - dynamic get webSocketProxyService => null; - @override Future close() => _closed ??= Future.wait([debugService.close(), dwdsVmClient.close()]); } + +/// WebSocket-based implementation of app debug services. +class WebSocketAppDebugServices implements IAppDebugServices { + final WebSocketDebugService _debugService; + final WebSocketDwdsVmClient _dwdsVmClient; + Future? _closed; + String? _connectedInstanceId; + + WebSocketAppDebugServices(this._debugService, this._dwdsVmClient); + + @override + WebSocketDebugService get debugService => _debugService; + + @override + WebSocketDwdsVmClient get dwdsVmClient => _dwdsVmClient; + + @override + String? get connectedInstanceId => _connectedInstanceId; + + @override + set connectedInstanceId(String? id) => _connectedInstanceId = id; + + // WebSocket-only service - Chrome/DDS features not available + @override + dynamic get dwdsStats => null; + @override + Uri? get ddsUri => null; + + @override + ProxyService get proxyService => _debugService.webSocketProxyService; + + @override + Future close() { + return _closed ??= Future.wait([ + debugService.close(), + dwdsVmClient.close(), + ]); + } +} diff --git a/dwds/lib/src/services/web_socket_app_debug_services.dart b/dwds/lib/src/services/web_socket_app_debug_services.dart deleted file mode 100644 index eb4799aec..000000000 --- a/dwds/lib/src/services/web_socket_app_debug_services.dart +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2025, 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. - -import 'dart:async'; -import 'package:dwds/src/services/app_debug_services.dart'; -import 'package:dwds/src/services/debug_service.dart'; -import 'package:dwds/src/web_socket_dwds_vm_client.dart'; - -/// WebSocket-based implementation of app debug services. -class WebSocketAppDebugServices implements IAppDebugServices { - final WebSocketDebugService _debugService; - final WebSocketDwdsVmClient _dwdsVmClient; - Future? _closed; - String? _connectedInstanceId; - - WebSocketAppDebugServices(this._debugService, this._dwdsVmClient); - - @override - WebSocketDebugService get debugService => _debugService; - - @override - WebSocketDwdsVmClient get dwdsVmClient => _dwdsVmClient; - - @override - String? get connectedInstanceId => _connectedInstanceId; - - @override - set connectedInstanceId(String? id) => _connectedInstanceId = id; - - // WebSocket-only service - Chrome/DDS features not available - @override - dynamic get dwdsStats => null; - @override - Uri? get ddsUri => null; - @override - dynamic get chromeProxyService => null; - - @override - dynamic get webSocketProxyService => _debugService.webSocketProxyService; - @override - Future close() { - return _closed ??= Future.wait([ - debugService.close(), - dwdsVmClient.close(), - ]); - } -} From 813765e5229d8ce77a38e935c860dd7467141b16 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 14:06:25 -0400 Subject: [PATCH 21/33] consolidate dwdsVmClient and WebSocketDwdsVmClient --- dwds/lib/src/dwds_vm_client.dart | 240 ++++++++++++++++-- dwds/lib/src/handlers/dev_handler.dart | 1 - dwds/lib/src/services/app_debug_services.dart | 7 +- dwds/lib/src/web_socket_dwds_vm_client.dart | 194 -------------- 4 files changed, 222 insertions(+), 220 deletions(-) delete mode 100644 dwds/lib/src/web_socket_dwds_vm_client.dart diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index 34285c718..ba8f64526 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -9,6 +9,7 @@ import 'package:dwds/src/events.dart'; import 'package:dwds/src/services/chrome_debug_exception.dart'; import 'package:dwds/src/services/chrome_proxy_service.dart'; import 'package:dwds/src/services/debug_service.dart'; +import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:dwds/src/utilities/synchronized.dart'; import 'package:logging/logging.dart'; import 'package:uuid/uuid.dart'; @@ -17,8 +18,6 @@ import 'package:vm_service/vm_service_io.dart'; import 'package:vm_service_interface/vm_service_interface.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; -final _logger = Logger('DwdsVmClient'); - /// Type of requests added to the request controller. typedef VmRequest = Map; @@ -38,9 +37,22 @@ enum _NamespacedServiceExtension { final String method; } +/// Common interface for DWDS VM clients. +abstract class IDwdsVmClient { + /// The VM service client. + VmService get client; + + /// Closes the VM client and releases resources. + Future close(); +} + +// Chrome-based DWDS VM client logger. +final _chromeLogger = Logger('DwdsVmClient'); + // A client of the vm service that registers some custom extensions like // hotRestart. -class DwdsVmClient { +class DwdsVmClient implements IDwdsVmClient { + @override final VmService client; final StreamController> _requestController; final StreamController> _responseController; @@ -137,7 +149,7 @@ class DwdsVmClient { }) { final client = VmService(responseStream.map(jsonEncode), (request) { if (requestController.isClosed) { - _logger.warning( + _chromeLogger.warning( 'Attempted to send a request but the connection is closed:\n\n' '$request', ); @@ -331,13 +343,13 @@ void _processSendEvent(Map request, DwdsStats dwdsStats) { switch (type) { case 'DevtoolsEvent': { - _logger.finest('Received DevTools event: $event'); + _chromeLogger.finest('Received DevTools event: $event'); final action = payload?['action'] as String?; final screen = payload?['screen'] as String?; if (screen != null && action == 'pageReady') { _recordDwdsStats(dwdsStats, screen); } else { - _logger.finest('Ignoring unknown event: $event'); + _chromeLogger.finest('Ignoring unknown event: $event'); } } } @@ -351,16 +363,16 @@ void _recordDwdsStats(DwdsStats dwdsStats, String screen) { final devToolLoadTime = DateTime.now().difference(devToolsStart).inMilliseconds; emitEvent(DwdsEvent.devToolsLoad(devToolLoadTime, screen)); - _logger.fine('DevTools load time: $devToolLoadTime ms'); + _chromeLogger.fine('DevTools load time: $devToolLoadTime ms'); } if (debuggerStart != null) { final debuggerReadyTime = DateTime.now().difference(debuggerStart).inMilliseconds; emitEvent(DwdsEvent.debuggerReady(debuggerReadyTime, screen)); - _logger.fine('Debugger ready time: $debuggerReadyTime ms'); + _chromeLogger.fine('Debugger ready time: $debuggerReadyTime ms'); } } else { - _logger.finest('Debugger and DevTools stats are already recorded.'); + _chromeLogger.finest('Debugger and DevTools stats are already recorded.'); } } @@ -381,14 +393,14 @@ Future> _hotRestart( ChromeProxyService chromeProxyService, VmService client, ) async { - _logger.info('Attempting a hot restart'); + _chromeLogger.info('Attempting a hot restart'); chromeProxyService.terminatingIsolates = true; await _disableBreakpointsAndResume(client, chromeProxyService); try { - _logger.info('Attempting to get execution context ID.'); + _chromeLogger.info('Attempting to get execution context ID.'); await tryGetContextId(chromeProxyService); - _logger.info('Got execution context ID.'); + _chromeLogger.info('Got execution context ID.'); } on StateError catch (e) { // We couldn't find the execution context. `hotRestart` may have been // triggered in the middle of a full reload. @@ -408,12 +420,12 @@ Future> _hotRestart( } // Generate run id to hot restart all apps loaded into the tab. final runId = const Uuid().v4().toString(); - _logger.info('Issuing \$dartHotRestartDwds request'); + _chromeLogger.info('Issuing \$dartHotRestartDwds request'); await chromeProxyService.inspector.jsEvaluate( '\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);', awaitPromise: true, ); - _logger.info('\$dartHotRestartDwds request complete.'); + _chromeLogger.info('\$dartHotRestartDwds request complete.'); } on WipError catch (exception) { final code = exception.error?['code']; final message = exception.error?['message']; @@ -433,11 +445,11 @@ Future> _hotRestart( }, }; } - _logger.info('Waiting for Isolate Start event.'); + _chromeLogger.info('Waiting for Isolate Start event.'); await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart); chromeProxyService.terminatingIsolates = false; - _logger.info('Successful hot restart'); + _chromeLogger.info('Successful hot restart'); return {'result': Success().toJson()}; } @@ -455,10 +467,10 @@ void _waitForResumeEventToRunMain(ChromeProxyService chromeProxyService) { Future> _fullReload( ChromeProxyService chromeProxyService, ) async { - _logger.info('Attempting a full reload'); + _chromeLogger.info('Attempting a full reload'); await chromeProxyService.remoteDebugger.enablePage(); await chromeProxyService.remoteDebugger.pageReload(); - _logger.info('Successful full reload'); + _chromeLogger.info('Successful full reload'); return {'result': Success().toJson()}; } @@ -466,7 +478,9 @@ Future _disableBreakpointsAndResume( VmService client, ChromeProxyService chromeProxyService, ) async { - _logger.info('Attempting to disable breakpoints and resume the isolate'); + _chromeLogger.info( + 'Attempting to disable breakpoints and resume the isolate', + ); final vm = await client.getVM(); final isolates = vm.isolates; if (isolates == null || isolates.isEmpty) { @@ -495,9 +509,193 @@ Future _disableBreakpointsAndResume( await client.resume(isolateId); } on RPCError catch (e, s) { if (!e.message.contains('Can only perform operation while paused')) { - _logger.severe('Hot restart failed to resume exiting isolate', e, s); + _chromeLogger.severe( + 'Hot restart failed to resume exiting isolate', + e, + s, + ); rethrow; } } - _logger.info('Successfully disabled breakpoints and resumed the isolate'); + _chromeLogger.info( + 'Successfully disabled breakpoints and resumed the isolate', + ); +} + +// WebSocket-based DWDS VM client logger. +final _webSocketLogger = Logger('WebSocketDwdsVmClient'); + +/// WebSocket-based DWDS VM client. +class WebSocketDwdsVmClient implements IDwdsVmClient { + @override + final VmService client; + final StreamController _requestController; + final StreamController _responseController; + Future? _closed; + + WebSocketDwdsVmClient( + this.client, + this._requestController, + this._responseController, + ); + + @override + Future close() => + _closed ??= () async { + await _requestController.close(); + await _responseController.close(); + await client.dispose(); + }(); + + static Future create( + WebSocketDebugService debugService, + ) async { + _webSocketLogger.fine('Creating WebSocket DWDS VM client'); + final webSocketProxyService = debugService.webSocketProxyService; + final responseController = StreamController(); + final responseSink = responseController.sink; + final responseStream = responseController.stream.asBroadcastStream(); + final requestController = StreamController(); + final requestSink = requestController.sink; + final requestStream = requestController.stream; + + _setUpWebSocketVmServerConnection( + webSocketProxyService: webSocketProxyService, + debugService: debugService, + responseStream: responseStream, + responseSink: responseSink, + requestStream: requestStream, + requestSink: requestSink, + ); + + final client = _setUpWebSocketVmClient( + responseStream: responseStream, + requestController: requestController, + requestSink: requestSink, + ); + + _webSocketLogger.fine('WebSocket DWDS VM client created successfully'); + return WebSocketDwdsVmClient(client, requestController, responseController); + } + + static VmService _setUpWebSocketVmClient({ + required Stream responseStream, + required StreamSink requestSink, + required StreamController requestController, + }) { + final client = VmService(responseStream.map(jsonEncode), (request) { + if (requestController.isClosed) { + _webSocketLogger.warning( + 'Attempted to send a request but the connection is closed:\n\n$request', + ); + return; + } + requestSink.add(Map.from(jsonDecode(request))); + }); + return client; + } + + static void _setUpWebSocketVmServerConnection({ + required WebSocketProxyService webSocketProxyService, + required WebSocketDebugService debugService, + required Stream responseStream, + required StreamSink responseSink, + required Stream requestStream, + required StreamSink requestSink, + }) { + responseStream.listen((request) async { + final response = await _maybeHandleWebSocketServiceExtensionRequest( + request, + webSocketProxyService: webSocketProxyService, + ); + if (response != null) { + requestSink.add(response); + } + }); + + final vmServerConnection = VmServerConnection( + requestStream, + responseSink, + debugService.serviceExtensionRegistry, + webSocketProxyService, + ); + + // Register service extensions + for (final extension in _NamespacedServiceExtension.values) { + _webSocketLogger.finest( + 'Registering service extension: ${extension.method}', + ); + debugService.serviceExtensionRegistry.registerExtension( + extension.method, + vmServerConnection, + ); + } + } + + static Future _maybeHandleWebSocketServiceExtensionRequest( + VmResponse request, { + required WebSocketProxyService webSocketProxyService, + }) async { + VmRequest? response; + final method = request['method']; + + _webSocketLogger.finest('Processing service extension method: $method'); + + if (method == _NamespacedServiceExtension.flutterListViews.method) { + response = await _webSocketFlutterListViewsHandler(webSocketProxyService); + } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { + response = _webSocketExtDwdsEmitEventHandler(request); + } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { + response = {'result': 'Reload not implemented'}; + } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { + response = _webSocketExtDwdsSendEventHandler(request); + } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { + response = {'result': 'Screenshot not implemented'}; + } + + if (response != null) { + response['id'] = request['id'] as String; + response['jsonrpc'] = '2.0'; + } + return response; + } + + static Future> _webSocketFlutterListViewsHandler( + WebSocketProxyService webSocketProxyService, + ) async { + final vm = await webSocketProxyService.getVM(); + _webSocketLogger.finest( + 'Retrieved VM with ${vm.isolates?.length ?? 0} isolates', + ); + final isolates = vm.isolates; + return { + 'result': { + 'views': [ + for (final isolate in isolates ?? []) + {'id': isolate.id, 'isolate': isolate.toJson()}, + ], + }, + }; + } + + static Map _webSocketExtDwdsEmitEventHandler( + VmResponse request, + ) { + final event = request['params'] as Map?; + if (event != null) { + final type = event['type'] as String?; + final payload = event['payload'] as Map?; + if (type != null && payload != null) { + _webSocketLogger.fine('EmitEvent: $type $payload'); + } + } + return {'result': 'EmitEvent handled'}; + } + + static Map _webSocketExtDwdsSendEventHandler( + VmResponse request, + ) { + _webSocketLogger.fine('SendEvent: $request'); + return {'result': 'SendEvent handled'}; + } } diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index b15233152..3fe15d3e2 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -36,7 +36,6 @@ import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/expression_compiler.dart'; import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:dwds/src/utilities/shared.dart'; -import 'package:dwds/src/web_socket_dwds_vm_client.dart'; import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; import 'package:sse/server/sse_handler.dart'; diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index 1f1986561..eb062346b 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -8,12 +8,11 @@ import 'package:dwds/src/services/chrome_proxy_service.dart' show ChromeProxyService; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/proxy_service.dart'; -import 'package:dwds/src/web_socket_dwds_vm_client.dart'; /// Common interface for debug service containers. abstract class IAppDebugServices { IDebugService get debugService; - dynamic get dwdsVmClient; + IDwdsVmClient get dwdsVmClient; dynamic get dwdsStats; Uri? get ddsUri; String? get connectedInstanceId; @@ -42,7 +41,7 @@ class AppDebugServices implements IAppDebugServices { DebugService get debugService => _debugService; @override - DwdsVmClient get dwdsVmClient => _dwdsVmClient; + IDwdsVmClient get dwdsVmClient => _dwdsVmClient; @override DwdsStats get dwdsStats => _dwdsStats; @@ -78,7 +77,7 @@ class WebSocketAppDebugServices implements IAppDebugServices { WebSocketDebugService get debugService => _debugService; @override - WebSocketDwdsVmClient get dwdsVmClient => _dwdsVmClient; + IDwdsVmClient get dwdsVmClient => _dwdsVmClient; @override String? get connectedInstanceId => _connectedInstanceId; diff --git a/dwds/lib/src/web_socket_dwds_vm_client.dart b/dwds/lib/src/web_socket_dwds_vm_client.dart deleted file mode 100644 index aaa94d07b..000000000 --- a/dwds/lib/src/web_socket_dwds_vm_client.dart +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) 2025, 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. - -import 'dart:async'; -import 'dart:convert'; - -import 'package:dwds/src/services/debug_service.dart'; -import 'package:dwds/src/services/web_socket_proxy_service.dart'; -import 'package:logging/logging.dart'; -import 'package:vm_service/vm_service.dart'; -import 'package:vm_service_interface/vm_service_interface.dart'; - -final _logger = Logger('WebSocketDwdsVmClient'); - -typedef VmRequest = Map; -typedef VmResponse = Map; - -enum _NamespacedServiceExtension { - extDwdsEmitEvent(method: 'ext.dwds.emitEvent'), - extDwdsReload(method: 'ext.dwds.reload'), - extDwdsRestart(method: 'ext.dwds.restart'), - extDwdsScreenshot(method: 'ext.dwds.screenshot'), - extDwdsSendEvent(method: 'ext.dwds.sendEvent'), - flutterListViews(method: '_flutter.listViews'); - - const _NamespacedServiceExtension({required this.method}); - final String method; -} - -/// WebSocket-based DWDS VM client. -class WebSocketDwdsVmClient { - final VmService client; - final StreamController _requestController; - final StreamController _responseController; - Future? _closed; - - WebSocketDwdsVmClient( - this.client, - this._requestController, - this._responseController, - ); - - Future close() => - _closed ??= () async { - await _requestController.close(); - await _responseController.close(); - await client.dispose(); - }(); - - static Future create( - WebSocketDebugService debugService, - ) async { - _logger.fine('Creating WebSocket DWDS VM client'); - final webSocketProxyService = debugService.webSocketProxyService; - final responseController = StreamController(); - final responseSink = responseController.sink; - final responseStream = responseController.stream.asBroadcastStream(); - final requestController = StreamController(); - final requestSink = requestController.sink; - final requestStream = requestController.stream; - - _setUpVmServerConnection( - webSocketProxyService: webSocketProxyService, - debugService: debugService, - responseStream: responseStream, - responseSink: responseSink, - requestStream: requestStream, - requestSink: requestSink, - ); - - final client = _setUpVmClient( - responseStream: responseStream, - requestController: requestController, - requestSink: requestSink, - ); - - _logger.fine('WebSocket DWDS VM client created successfully'); - return WebSocketDwdsVmClient(client, requestController, responseController); - } - - static VmService _setUpVmClient({ - required Stream responseStream, - required StreamSink requestSink, - required StreamController requestController, - }) { - final client = VmService(responseStream.map(jsonEncode), (request) { - if (requestController.isClosed) { - _logger.warning( - 'Attempted to send a request but the connection is closed:\n\n$request', - ); - return; - } - requestSink.add(Map.from(jsonDecode(request))); - }); - return client; - } - - static void _setUpVmServerConnection({ - required WebSocketProxyService webSocketProxyService, - required WebSocketDebugService debugService, - required Stream responseStream, - required StreamSink responseSink, - required Stream requestStream, - required StreamSink requestSink, - }) { - responseStream.listen((request) async { - final response = await _maybeHandleServiceExtensionRequest( - request, - webSocketProxyService: webSocketProxyService, - ); - if (response != null) { - requestSink.add(response); - } - }); - - final vmServerConnection = VmServerConnection( - requestStream, - responseSink, - debugService.serviceExtensionRegistry, - webSocketProxyService, - ); - - // Register service extensions - for (final extension in _NamespacedServiceExtension.values) { - _logger.finest('Registering service extension: ${extension.method}'); - debugService.serviceExtensionRegistry.registerExtension( - extension.method, - vmServerConnection, - ); - } - } - - static Future _maybeHandleServiceExtensionRequest( - VmResponse request, { - required WebSocketProxyService webSocketProxyService, - }) async { - VmRequest? response; - final method = request['method']; - - _logger.finest('Processing service extension method: $method'); - - if (method == _NamespacedServiceExtension.flutterListViews.method) { - response = await _flutterListViewsHandler(webSocketProxyService); - } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { - response = _extDwdsEmitEventHandler(request); - } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { - response = {'result': 'Reload not implemented'}; - } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { - response = _extDwdsSendEventHandler(request); - } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { - response = {'result': 'Screenshot not implemented'}; - } - - if (response != null) { - response['id'] = request['id'] as String; - response['jsonrpc'] = '2.0'; - } - return response; - } - - static Future> _flutterListViewsHandler( - WebSocketProxyService webSocketProxyService, - ) async { - final vm = await webSocketProxyService.getVM(); - _logger.finest('Retrieved VM with ${vm.isolates?.length ?? 0} isolates'); - final isolates = vm.isolates; - return { - 'result': { - 'views': [ - for (final isolate in isolates ?? []) - {'id': isolate.id, 'isolate': isolate.toJson()}, - ], - }, - }; - } - - static Map _extDwdsEmitEventHandler(VmResponse request) { - final event = request['params'] as Map?; - if (event != null) { - final type = event['type'] as String?; - final payload = event['payload'] as Map?; - if (type != null && payload != null) { - _logger.fine('EmitEvent: $type $payload'); - } - } - return {'result': 'EmitEvent handled'}; - } - - static Map _extDwdsSendEventHandler(VmResponse request) { - _logger.fine('SendEvent: $request'); - return {'result': 'SendEvent handled'}; - } -} From 14e2a2a9319953471364e20e18f9eab7ad19fe22 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 14:47:18 -0400 Subject: [PATCH 22/33] refactored can remove duplicate methods --- dwds/lib/src/dwds_vm_client.dart | 407 +++++++++++++++---------------- 1 file changed, 190 insertions(+), 217 deletions(-) diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index ba8f64526..c1736ae9b 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -9,6 +9,7 @@ import 'package:dwds/src/events.dart'; import 'package:dwds/src/services/chrome_debug_exception.dart'; import 'package:dwds/src/services/chrome_proxy_service.dart'; import 'package:dwds/src/services/debug_service.dart'; +import 'package:dwds/src/services/proxy_service.dart'; import 'package:dwds/src/services/web_socket_proxy_service.dart'; import 'package:dwds/src/utilities/synchronized.dart'; import 'package:logging/logging.dart'; @@ -217,16 +218,20 @@ class DwdsVmClient implements IDwdsVmClient { VmRequest? response; final method = request['method']; if (method == _NamespacedServiceExtension.flutterListViews.method) { - response = await _flutterListViewsHandler(chromeProxyService); + response = await flutterListViewsHandler(chromeProxyService); } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { - response = _extDwdsEmitEventHandler(request); + response = extDwdsEmitEventHandler(request, _chromeLogger); } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { response = await _extDwdsReloadHandler(chromeProxyService); } else if (method == _NamespacedServiceExtension.extDwdsRestart.method) { final client = await clientFuture; response = await _extDwdsRestartHandler(chromeProxyService, client); } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { - response = await _extDwdsSendEventHandler(request, dwdsStats); + response = await extDwdsSendEventHandler( + request, + dwdsStats, + _chromeLogger, + ); } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { response = await _extDwdsScreenshotHandler(chromeProxyService); } @@ -242,21 +247,6 @@ class DwdsVmClient implements IDwdsVmClient { return response; } - static Future> _flutterListViewsHandler( - ChromeProxyService chromeProxyService, - ) async { - final vm = await chromeProxyService.getVM(); - final isolates = vm.isolates; - return { - 'result': { - 'views': [ - for (final isolate in isolates ?? []) - {'id': isolate.id, 'isolate': isolate.toJson()}, - ], - }, - }; - } - static Future> _extDwdsScreenshotHandler( ChromeProxyService chromeProxyService, ) async { @@ -267,27 +257,6 @@ class DwdsVmClient implements IDwdsVmClient { return {'result': response.result as Object}; } - static Future> _extDwdsSendEventHandler( - VmResponse request, - DwdsStats dwdsStats, - ) async { - _processSendEvent(request, dwdsStats); - return {'result': Success().toJson()}; - } - - static Map _extDwdsEmitEventHandler(VmResponse request) { - final event = request['params'] as Map?; - if (event != null) { - final type = event['type'] as String?; - final payload = event['payload'] as Map?; - if (type != null && payload != null) { - emitEvent(DwdsEvent(type, payload)); - } - } - - return {'result': Success().toJson()}; - } - static Future> _extDwdsReloadHandler( ChromeProxyService chromeProxyService, ) async { @@ -335,6 +304,188 @@ class DwdsVmClient implements IDwdsVmClient { } } +// WebSocket-based DWDS VM client logger. +final _webSocketLogger = Logger('WebSocketDwdsVmClient'); + +/// WebSocket-based DWDS VM client. +class WebSocketDwdsVmClient implements IDwdsVmClient { + @override + final VmService client; + final StreamController _requestController; + final StreamController _responseController; + Future? _closed; + + WebSocketDwdsVmClient( + this.client, + this._requestController, + this._responseController, + ); + + @override + Future close() => + _closed ??= () async { + await _requestController.close(); + await _responseController.close(); + await client.dispose(); + }(); + + static Future create( + WebSocketDebugService debugService, + ) async { + _webSocketLogger.fine('Creating WebSocket DWDS VM client'); + final webSocketProxyService = debugService.webSocketProxyService; + final responseController = StreamController(); + final responseSink = responseController.sink; + final responseStream = responseController.stream.asBroadcastStream(); + final requestController = StreamController(); + final requestSink = requestController.sink; + final requestStream = requestController.stream; + + _setUpWebSocketVmServerConnection( + webSocketProxyService: webSocketProxyService, + debugService: debugService, + responseStream: responseStream, + responseSink: responseSink, + requestStream: requestStream, + requestSink: requestSink, + ); + + final client = _setUpWebSocketVmClient( + responseStream: responseStream, + requestController: requestController, + requestSink: requestSink, + ); + + _webSocketLogger.fine('WebSocket DWDS VM client created successfully'); + return WebSocketDwdsVmClient(client, requestController, responseController); + } + + static VmService _setUpWebSocketVmClient({ + required Stream responseStream, + required StreamSink requestSink, + required StreamController requestController, + }) { + final client = VmService(responseStream.map(jsonEncode), (request) { + if (requestController.isClosed) { + _webSocketLogger.warning( + 'Attempted to send a request but the connection is closed:\n\n$request', + ); + return; + } + requestSink.add(Map.from(jsonDecode(request))); + }); + return client; + } + + static void _setUpWebSocketVmServerConnection({ + required WebSocketProxyService webSocketProxyService, + required WebSocketDebugService debugService, + required Stream responseStream, + required StreamSink responseSink, + required Stream requestStream, + required StreamSink requestSink, + }) { + responseStream.listen((request) async { + final response = await _maybeHandleWebSocketServiceExtensionRequest( + request, + webSocketProxyService: webSocketProxyService, + ); + if (response != null) { + requestSink.add(response); + } + }); + + final vmServerConnection = VmServerConnection( + requestStream, + responseSink, + debugService.serviceExtensionRegistry, + webSocketProxyService, + ); + + // Register service extensions + for (final extension in _NamespacedServiceExtension.values) { + _webSocketLogger.finest( + 'Registering service extension: ${extension.method}', + ); + debugService.serviceExtensionRegistry.registerExtension( + extension.method, + vmServerConnection, + ); + } + } + + static Future _maybeHandleWebSocketServiceExtensionRequest( + VmResponse request, { + required WebSocketProxyService webSocketProxyService, + }) async { + VmRequest? response; + final method = request['method']; + + _webSocketLogger.finest('Processing service extension method: $method'); + + if (method == _NamespacedServiceExtension.flutterListViews.method) { + response = await flutterListViewsHandler(webSocketProxyService); + } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { + response = extDwdsEmitEventHandler(request, _webSocketLogger); + } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { + response = {'result': 'Reload not implemented'}; + } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { + response = await extDwdsSendEventHandler(request, null, _webSocketLogger); + } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { + response = {'result': 'Screenshot not implemented'}; + } + + if (response != null) { + response['id'] = request['id'] as String; + response['jsonrpc'] = '2.0'; + } + return response; + } +} + +/// Shared handler for Flutter list views service extension. +Future> flutterListViewsHandler( + ProxyService proxyService, +) async { + final vm = await proxyService.getVM(); + final isolates = vm.isolates; + return { + 'result': { + 'views': [ + for (final isolate in isolates ?? []) + {'id': isolate.id, 'isolate': isolate.toJson()}, + ], + }, + }; +} + +/// Shared handler for DWDS emit event service extension. +Map extDwdsEmitEventHandler(VmResponse request, Logger logger) { + final event = request['params'] as Map?; + if (event != null) { + final type = event['type'] as String?; + final payload = event['payload'] as Map?; + if (type != null && payload != null) { + logger.fine('EmitEvent: $type $payload'); + emitEvent(DwdsEvent(type, payload)); + } + } + return {'result': Success().toJson()}; +} + +/// Shared handler for DWDS send event service extension. +Future> extDwdsSendEventHandler( + VmResponse request, + DwdsStats? dwdsStats, + Logger logger, +) async { + logger.fine('SendEvent: $request'); + if (dwdsStats != null) { + _processSendEvent(request, dwdsStats); + } + return {'result': Success().toJson()}; +} + void _processSendEvent(Map request, DwdsStats dwdsStats) { final event = request['params'] as Map?; if (event == null) return; @@ -521,181 +672,3 @@ Future _disableBreakpointsAndResume( 'Successfully disabled breakpoints and resumed the isolate', ); } - -// WebSocket-based DWDS VM client logger. -final _webSocketLogger = Logger('WebSocketDwdsVmClient'); - -/// WebSocket-based DWDS VM client. -class WebSocketDwdsVmClient implements IDwdsVmClient { - @override - final VmService client; - final StreamController _requestController; - final StreamController _responseController; - Future? _closed; - - WebSocketDwdsVmClient( - this.client, - this._requestController, - this._responseController, - ); - - @override - Future close() => - _closed ??= () async { - await _requestController.close(); - await _responseController.close(); - await client.dispose(); - }(); - - static Future create( - WebSocketDebugService debugService, - ) async { - _webSocketLogger.fine('Creating WebSocket DWDS VM client'); - final webSocketProxyService = debugService.webSocketProxyService; - final responseController = StreamController(); - final responseSink = responseController.sink; - final responseStream = responseController.stream.asBroadcastStream(); - final requestController = StreamController(); - final requestSink = requestController.sink; - final requestStream = requestController.stream; - - _setUpWebSocketVmServerConnection( - webSocketProxyService: webSocketProxyService, - debugService: debugService, - responseStream: responseStream, - responseSink: responseSink, - requestStream: requestStream, - requestSink: requestSink, - ); - - final client = _setUpWebSocketVmClient( - responseStream: responseStream, - requestController: requestController, - requestSink: requestSink, - ); - - _webSocketLogger.fine('WebSocket DWDS VM client created successfully'); - return WebSocketDwdsVmClient(client, requestController, responseController); - } - - static VmService _setUpWebSocketVmClient({ - required Stream responseStream, - required StreamSink requestSink, - required StreamController requestController, - }) { - final client = VmService(responseStream.map(jsonEncode), (request) { - if (requestController.isClosed) { - _webSocketLogger.warning( - 'Attempted to send a request but the connection is closed:\n\n$request', - ); - return; - } - requestSink.add(Map.from(jsonDecode(request))); - }); - return client; - } - - static void _setUpWebSocketVmServerConnection({ - required WebSocketProxyService webSocketProxyService, - required WebSocketDebugService debugService, - required Stream responseStream, - required StreamSink responseSink, - required Stream requestStream, - required StreamSink requestSink, - }) { - responseStream.listen((request) async { - final response = await _maybeHandleWebSocketServiceExtensionRequest( - request, - webSocketProxyService: webSocketProxyService, - ); - if (response != null) { - requestSink.add(response); - } - }); - - final vmServerConnection = VmServerConnection( - requestStream, - responseSink, - debugService.serviceExtensionRegistry, - webSocketProxyService, - ); - - // Register service extensions - for (final extension in _NamespacedServiceExtension.values) { - _webSocketLogger.finest( - 'Registering service extension: ${extension.method}', - ); - debugService.serviceExtensionRegistry.registerExtension( - extension.method, - vmServerConnection, - ); - } - } - - static Future _maybeHandleWebSocketServiceExtensionRequest( - VmResponse request, { - required WebSocketProxyService webSocketProxyService, - }) async { - VmRequest? response; - final method = request['method']; - - _webSocketLogger.finest('Processing service extension method: $method'); - - if (method == _NamespacedServiceExtension.flutterListViews.method) { - response = await _webSocketFlutterListViewsHandler(webSocketProxyService); - } else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) { - response = _webSocketExtDwdsEmitEventHandler(request); - } else if (method == _NamespacedServiceExtension.extDwdsReload.method) { - response = {'result': 'Reload not implemented'}; - } else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) { - response = _webSocketExtDwdsSendEventHandler(request); - } else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) { - response = {'result': 'Screenshot not implemented'}; - } - - if (response != null) { - response['id'] = request['id'] as String; - response['jsonrpc'] = '2.0'; - } - return response; - } - - static Future> _webSocketFlutterListViewsHandler( - WebSocketProxyService webSocketProxyService, - ) async { - final vm = await webSocketProxyService.getVM(); - _webSocketLogger.finest( - 'Retrieved VM with ${vm.isolates?.length ?? 0} isolates', - ); - final isolates = vm.isolates; - return { - 'result': { - 'views': [ - for (final isolate in isolates ?? []) - {'id': isolate.id, 'isolate': isolate.toJson()}, - ], - }, - }; - } - - static Map _webSocketExtDwdsEmitEventHandler( - VmResponse request, - ) { - final event = request['params'] as Map?; - if (event != null) { - final type = event['type'] as String?; - final payload = event['payload'] as Map?; - if (type != null && payload != null) { - _webSocketLogger.fine('EmitEvent: $type $payload'); - } - } - return {'result': 'EmitEvent handled'}; - } - - static Map _webSocketExtDwdsSendEventHandler( - VmResponse request, - ) { - _webSocketLogger.fine('SendEvent: $request'); - return {'result': 'SendEvent handled'}; - } -} From dea7f56a4a1128d955e020cbc359b462cad9ef37 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 14:51:46 -0400 Subject: [PATCH 23/33] remove use of dynamic --- dwds/lib/src/services/app_debug_services.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index eb062346b..122f5cb9a 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -13,7 +13,7 @@ import 'package:dwds/src/services/proxy_service.dart'; abstract class IAppDebugServices { IDebugService get debugService; IDwdsVmClient get dwdsVmClient; - dynamic get dwdsStats; + DwdsStats? get dwdsStats; Uri? get ddsUri; String? get connectedInstanceId; set connectedInstanceId(String? id); @@ -87,7 +87,7 @@ class WebSocketAppDebugServices implements IAppDebugServices { // WebSocket-only service - Chrome/DDS features not available @override - dynamic get dwdsStats => null; + DwdsStats? get dwdsStats => null; @override Uri? get ddsUri => null; From 5340ab0a30f57c38ec70f5770f891c90df294995 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 15:41:15 -0400 Subject: [PATCH 24/33] fix dev_handler error --- dwds/lib/src/handlers/dev_handler.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 3fe15d3e2..0996f615c 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -569,7 +569,7 @@ class DevHandler { ); appServices.connectedInstanceId = appConnection.request.instanceId; - appServices.dwdsStats.updateLoadTime( + appServices.dwdsStats!.updateLoadTime( debuggerStart: debuggerStart, devToolsStart: DateTime.now(), ); @@ -1039,7 +1039,7 @@ class DevHandler { final encodedUri = await appServices.debugService.encodedUri; - appServices.dwdsStats.updateLoadTime( + appServices.dwdsStats!.updateLoadTime( debuggerStart: debuggerStart, devToolsStart: DateTime.now(), ); From be31caa047a4df07f86a211e067b9a920f648b65 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 17:04:06 -0400 Subject: [PATCH 25/33] consolidate handleChromeMessages and handleWebSocketMessages created default createIsolate and destroyIsolate in ProxyService --- dwds/lib/src/handlers/dev_handler.dart | 74 ++++++------------- .../src/services/chrome_proxy_service.dart | 2 + dwds/lib/src/services/proxy_service.dart | 34 +++++++++ .../services/web_socket_proxy_service.dart | 14 +++- 4 files changed, 69 insertions(+), 55 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 0996f615c..01ecf0eea 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -141,6 +141,7 @@ class DevHandler { /// Sends the provided [request] to all connected injected clients. /// Returns the number of clients the request was successfully sent to. int _sendRequestToClients(Object request) { + _logger.finest('Sending request to injected clients: $request'); var successfulSends = 0; for (final injectedConnection in _injectedConnections) { try { @@ -358,12 +359,9 @@ class DevHandler { } if (message is DevToolsRequest) { await _handleDebugRequest(connection, injectedConnection); - } else if (useWebSocketConnection) { - // Handle WebSocket-specific messages - await _handleWebSocketMessage(connection, message); } else { - // Handle Chrome-specific messages - await _handleChromeMessage(connection, message); + // Handle messages for both WebSocket and Chrome proxy services + await _handleMessage(connection, message); } } } catch (e, s) { @@ -413,66 +411,40 @@ class DevHandler { ); } - /// Handles WebSocket-specific messages. - Future _handleWebSocketMessage( - AppConnection connection, - Object? message, - ) async { + /// Handles messages for both WebSocket and Chrome proxy services. + Future _handleMessage(AppConnection connection, Object? message) async { if (message == null) return; final appId = connection.request.appId; final proxyService = _servicesByAppId[appId]?.proxyService; - final wsService = - proxyService is WebSocketProxyService ? proxyService : null; - if (wsService == null) { + if (proxyService == null) { _logger.warning( - 'No WebSocketProxyService found for appId: $appId to process $message', + 'No proxy service found for appId: $appId to process $message', ); return; } + + // Handle messages that are specific to certain proxy service types if (message is HotReloadResponse) { - wsService.completeHotReload(message); + proxyService.completeHotReload(message); } else if (message is ServiceExtensionResponse) { - wsService.completeServiceExtension(message); - } else if (message is RegisterEvent) { - wsService.parseRegisterEvent(message); - } else if (message is BatchedDebugEvents) { - wsService.parseBatchedDebugEvents(message); - } else if (message is DebugEvent) { - wsService.parseDebugEvent(message); - } else { - throw UnsupportedError( - 'Message type ${message.runtimeType} is not supported in WebSocket mode', - ); - } - } - - /// Handles Chrome-specific messages. - Future _handleChromeMessage( - AppConnection connection, - Object? message, - ) async { - if (message == null) return; - - final appId = connection.request.appId; - final proxyService = _servicesByAppId[appId]?.proxyService; - final chromeService = - proxyService is ChromeProxyService ? proxyService : null; - - if (message is IsolateExit) { + proxyService.completeServiceExtension(message); + } else if (message is IsolateExit) { _handleIsolateExit(connection); } else if (message is IsolateStart) { await _handleIsolateStart(connection); } else if (message is BatchedDebugEvents) { - chromeService?.parseBatchedDebugEvents(message); + proxyService.parseBatchedDebugEvents(message); } else if (message is DebugEvent) { - chromeService?.parseDebugEvent(message); + proxyService.parseDebugEvent(message); } else if (message is RegisterEvent) { - chromeService?.parseRegisterEvent(message); + proxyService.parseRegisterEvent(message); } else { + final serviceType = + proxyService is WebSocketProxyService ? 'WebSocket' : 'Chrome'; throw UnsupportedError( - 'Message type ${message.runtimeType} is not supported in Chrome mode', + 'Message type ${message.runtimeType} is not supported in $serviceType mode', ); } } @@ -649,12 +621,12 @@ class DevHandler { // AppConnection is in the KeepAlive state (this means it disconnected but // is still waiting for a possible reconnect - this happens during a page // reload). - final canReuseConnection = + final canReconnect = services != null && (services.connectedInstanceId == null || existingConnection?.isInKeepAlivePeriod == true); - if (canReuseConnection) { + if (canReconnect) { // Disconnect any old connection (eg. those in the keep-alive waiting // state when reloading the page). existingConnection?.shutDown(); @@ -719,13 +691,13 @@ class DevHandler { final hasNoActiveConnection = services?.connectedInstanceId == null; final noExistingConnection = existingConnection == null; - final canReuseConnection = + final canReconnect = services != null && (isSameInstance || (isKeepAliveReconnect && hasNoActiveConnection) || (noExistingConnection && hasNoActiveConnection)); - if (canReuseConnection) { + if (canReconnect) { // Reconnect to existing service. await _reconnectToService( services, @@ -739,7 +711,6 @@ class DevHandler { readyToRunMainCompleter.complete(); // For WebSocket mode, we need to proactively create and emit a debug connection - // since Flutter tools won't call debugConnection() for WebServerDevice try { // Initialize the WebSocket service and create debug connection final debugConnection = await createDebugConnectionForWebSocket( @@ -747,7 +718,6 @@ class DevHandler { ); // Emit the debug connection through the extension stream - // This should trigger Flutter tools to pick it up as if it was an extension connection extensionDebugConnections.add(debugConnection); } catch (e, s) { _logger.warning('Failed to create WebSocket debug connection: $e\n$s'); diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 0d1abefec..2774fb68c 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -262,6 +262,7 @@ class ChromeProxyService extends ProxyService { /// /// If [newConnection] is true, this method does not recompute metadata /// information as the metadata couldn't have changed. + @override Future createIsolate( AppConnection appConnection, { bool newConnection = false, @@ -381,6 +382,7 @@ class ChromeProxyService extends ProxyService { /// Should be called when there is a hot restart or full page refresh. /// /// Clears out the [_inspector] and all related cached information. + @override void destroyIsolate() { _logger.fine('Destroying isolate'); if (!_isIsolateRunning) return; diff --git a/dwds/lib/src/services/proxy_service.dart b/dwds/lib/src/services/proxy_service.dart index 93f17656a..b7eb84b99 100644 --- a/dwds/lib/src/services/proxy_service.dart +++ b/dwds/lib/src/services/proxy_service.dart @@ -5,7 +5,10 @@ import 'dart:async'; import 'package:dwds/data/debug_event.dart'; +import 'package:dwds/data/hot_reload_response.dart'; import 'package:dwds/data/register_event.dart'; +import 'package:dwds/data/service_extension_response.dart'; +import 'package:dwds/src/connections/app_connection.dart'; import 'package:dwds/src/events.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:pub_semver/pub_semver.dart' as semver; @@ -182,6 +185,22 @@ abstract class ProxyService implements VmServiceInterface { /// protocol [Event]. void parseRegisterEvent(RegisterEvent registerEvent); + /// Completes hot reload with response from client. + /// + /// Default implementation throws UnimplementedError. + /// Override in subclasses that support hot reload completion. + void completeHotReload(HotReloadResponse response) { + throw UnimplementedError('completeHotReload not supported'); + } + + /// Completes service extension with response from client. + /// + /// Default implementation throws UnimplementedError. + /// Override in subclasses that support service extension completion. + void completeServiceExtension(ServiceExtensionResponse response) { + throw UnimplementedError('completeServiceExtension not supported'); + } + /// Standard RPC error for unsupported methods. static vm_service.RPCError _rpcNotSupported(String method) { return vm_service.RPCError( @@ -347,6 +366,21 @@ abstract class ProxyService implements VmServiceInterface { return _rpcNotSupportedFuture('clearCpuSamples'); } + /// Creates a new isolate for debugging. + /// + /// Implementations should handle isolate lifecycle management according to + /// their specific debugging mode (Chrome vs WebSocket). + Future createIsolate( + AppConnection appConnection, { + bool newConnection = false, + }); + + /// Destroys the isolate and cleans up debugging state. + /// + /// Implementations should handle cleanup according to their specific + /// debugging mode and connection management strategy. + void destroyIsolate(); + /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service /// are unimplemented in DWDS. @override diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index b66fffe6c..c345bf07c 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -122,15 +122,20 @@ class WebSocketProxyService extends ProxyService { bool _mainHasStarted = false; /// Creates a new isolate for WebSocket debugging. - Future createIsolate([AppConnection? appConnectionOverride]) async { - // Update app connection if override provided - appConnection = appConnectionOverride ?? appConnection; + @override + Future createIsolate( + AppConnection appConnection, { + bool newConnection = false, + }) async { + // Update app connection + this.appConnection = appConnection; // Track this connection final connectionId = appConnection.request.instanceId; // Check if this connection is already being tracked final isNewConnection = + newConnection || !_appConnectionDoneSubscriptions.containsKey(connectionId); if (isNewConnection) { @@ -265,6 +270,7 @@ class WebSocketProxyService extends ProxyService { } /// Destroys the isolate and cleans up state. + @override void destroyIsolate() { _logger.fine('Destroying isolate'); @@ -452,6 +458,7 @@ class WebSocketProxyService extends ProxyService { } /// Completes hot reload with response from client. + @override void completeHotReload(HotReloadResponse response) { final tracker = _pendingHotReloads[response.id]; @@ -630,6 +637,7 @@ class WebSocketProxyService extends ProxyService { } /// Completes service extension with response. + @override void completeServiceExtension(ServiceExtensionResponse response) { final id = response.id; From cf526af11e139125bd08dfab35ad225b016c5f30 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 22 Jul 2025 17:20:10 -0400 Subject: [PATCH 26/33] updated logg message in _sendRequestToClients --- dwds/lib/src/handlers/dev_handler.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 01ecf0eea..85b0d927c 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -150,16 +150,11 @@ class DevHandler { } on StateError catch (e) { // The sink has already closed (app is disconnected), or another StateError occurred. _logger.warning( - 'Failed to send request to client ${injectedConnection.hashCode}, ' - 'connection likely closed. Error: $e', + 'Failed to send request to client, connection likely closed. Error: $e', ); } catch (e, s) { // Catch any other potential errors during sending. - _logger.severe( - 'Error sending request to client ${injectedConnection.hashCode}: $e', - e, - s, - ); + _logger.severe('Error sending request to client: $e', e, s); } } _logger.fine( From 49375085254071b3c000969dd691f47766c8c2db Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 11:29:39 -0400 Subject: [PATCH 27/33] updated dev_handler to throw error if proxy_service is not the right type --- dwds/lib/src/handlers/dev_handler.dart | 25 ++++++++++++++++--------- dwds/lib/src/injected/client.js | 20 ++++++++++++++------ dwds/web/reloader/manager.dart | 2 +- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 85b0d927c..cd656678d 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -559,12 +559,18 @@ class DevHandler { final appDebugServices = await loadAppServices(appConnection); // Initialize WebSocket proxy service - final proxyService = appDebugServices.proxyService; - if (proxyService is WebSocketProxyService) { + try { + final proxyService = appDebugServices.proxyService; + if (proxyService is! WebSocketProxyService) { + throw StateError( + 'Expected WebSocketProxyService but got ${proxyService.runtimeType}. ', + ); + } await proxyService.isInitialized; _logger.fine('WebSocket proxy service initialized successfully'); - } else { - _logger.warning('WebSocket proxy service is null'); + } catch (e) { + _logger.severe('Failed to initialize WebSocket proxy service: $e'); + rethrow; } return DebugConnection(appDebugServices); @@ -579,12 +585,13 @@ class DevHandler { // Initialize Chrome proxy service try { final proxyService = appDebugServices.proxyService; - if (proxyService is ChromeProxyService) { - await proxyService.isInitialized; - _logger.fine('Chrome proxy service initialized successfully'); - } else { - _logger.warning('Chrome proxy service is null'); + if (proxyService is! ChromeProxyService) { + throw StateError( + 'Expected ChromeProxyService but got ${proxyService.runtimeType}. ', + ); } + await proxyService.isInitialized; + _logger.fine('Chrome proxy service initialized successfully'); } catch (e) { _logger.severe('Failed to initialize Chrome proxy service: $e'); rethrow; diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index 31776c0f4..b2b637588 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -27884,12 +27884,20 @@ case 0: // Function start restarter = $async$self._restarter; - if (restarter instanceof A.DdcLibraryBundleRestarter) { - $async$returnValue = restarter.handleServiceExtension$2(method, args); - // goto return - $async$goto = 1; - break; - } + $async$goto = restarter instanceof A.DdcLibraryBundleRestarter ? 3 : 4; + break; + case 3: + // then + $async$goto = 5; + return A._asyncAwait(restarter.handleServiceExtension$2(method, args), $async$handleServiceExtension$2); + case 5: + // returning from await. + $async$returnValue = $async$result; + // goto return + $async$goto = 1; + break; + case 4: + // join $async$returnValue = null; // goto return $async$goto = 1; diff --git a/dwds/web/reloader/manager.dart b/dwds/web/reloader/manager.dart index 2267ef9f5..12e63b147 100644 --- a/dwds/web/reloader/manager.dart +++ b/dwds/web/reloader/manager.dart @@ -78,7 +78,7 @@ class ReloadingManager { ) async { final restarter = _restarter; if (restarter is DdcLibraryBundleRestarter) { - return restarter.handleServiceExtension(method, args); + return await restarter.handleServiceExtension(method, args); } // For other restarter types, return null to indicate not supported return null; From 3d0d4ead368f13869650d8d2e9b06a1c65dc748e Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 14:07:02 -0400 Subject: [PATCH 28/33] addressing analyzer issues --- dwds/lib/src/dwds_vm_client.dart | 1 + .../src/services/chrome_proxy_service.dart | 5 ++- dwds/lib/src/services/proxy_service.dart | 7 +--- .../services/web_socket_proxy_service.dart | 38 +++++-------------- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index c1736ae9b..030785913 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -68,6 +68,7 @@ class DwdsVmClient implements IDwdsVmClient { DwdsVmClient(this.client, this._requestController, this._responseController); + @override Future close() => _closed ??= () async { await _requestController.close(); diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index f470d9fdc..faba02e59 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -94,7 +94,7 @@ class ChromeProxyService extends ProxyService { bool terminatingIsolates = false; ChromeProxyService._( - VM vm, + super.vm, this.root, this._assetReader, this.remoteDebugger, @@ -103,7 +103,7 @@ class ChromeProxyService extends ProxyService { this._skipLists, this.executionContext, this._compiler, - ) : super(vm) { + ) { final debugger = Debugger.create( remoteDebugger, streamNotify, @@ -1370,6 +1370,7 @@ class ChromeProxyService extends ProxyService { /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service /// protocol [Event]s. + @override void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) { for (final debugEvent in debugEvents.events) { parseDebugEvent(debugEvent); diff --git a/dwds/lib/src/services/proxy_service.dart b/dwds/lib/src/services/proxy_service.dart index b7eb84b99..7af76f08e 100644 --- a/dwds/lib/src/services/proxy_service.dart +++ b/dwds/lib/src/services/proxy_service.dart @@ -28,8 +28,8 @@ abstract class ProxyService implements VmServiceInterface { final vm_service.VM _vm; /// Signals when isolate is initialized. - Future get isInitialized => _initializedCompleter.future; - Completer _initializedCompleter = Completer(); + Future get isInitialized => initializedCompleter.future; + Completer initializedCompleter = Completer(); /// The flags that can be set at runtime via [setFlag] and their respective /// values. @@ -67,9 +67,6 @@ abstract class ProxyService implements VmServiceInterface { StreamController get resumeAfterRestartEventsController => _resumeAfterRestartEventsController; Map get currentVmServiceFlags => _currentVmServiceFlags; - Completer get initializedCompleter => _initializedCompleter; - set initializedCompleter(Completer completer) => - _initializedCompleter = completer; ProxyService(this._vm); diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index c345bf07c..c0f07276e 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -75,7 +75,7 @@ class _ServiceExtensionTracker { responses.add(response); } - bool get allSuccessful => responses.every((r) => r.success == true); + bool get allSuccessful => responses.every((r) => r.success); void dispose() { timeoutTimer.cancel(); @@ -659,9 +659,7 @@ class WebSocketProxyService extends ProxyService { if (tracker.allSuccessful) { tracker.completer.complete(response); } else { - final failedResponses = tracker.responses.where( - (r) => r.success != true, - ); + final failedResponses = tracker.responses.where((r) => !r.success); final errorMessages = failedResponses .map((r) => r.errorMessage ?? 'Unknown error') .join('; '); @@ -696,6 +694,7 @@ class WebSocketProxyService extends ProxyService { /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service /// protocol [Event]s. + @override void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) { for (final debugEvent in debugEvents.events) { parseDebugEvent(debugEvent); @@ -770,7 +769,7 @@ class WebSocketProxyService extends ProxyService { ); Future _lookupResolvedPackageUris( - String isolateId, + String _, List uris, ) async { await isInitialized; @@ -782,7 +781,7 @@ class WebSocketProxyService extends ProxyService { Future pause(String isolateId) => wrapInErrorHandlerAsync('pause', () => _pause(isolateId)); - Future _pause(String isolateId) async { + Future _pause(String _) async { // Create a pause event and store it if (_isolateRef != null) { final pauseEvent = vm_service.Event( @@ -800,16 +799,9 @@ class WebSocketProxyService extends ProxyService { /// Resumes execution of the isolate. @override Future resume(String isolateId, {String? step, int? frameIndex}) => - wrapInErrorHandlerAsync( - 'resume', - () => _resume(isolateId, step: step, frameIndex: frameIndex), - ); + wrapInErrorHandlerAsync('resume', () => _resume(isolateId)); - Future _resume( - String isolateId, { - String? step, - int? frameIndex, - }) async { + Future _resume(String isolateId) async { if (hasPendingRestart && !resumeAfterRestartEventsController.isClosed) { resumeAfterRestartEventsController.add(isolateId); } else { @@ -848,10 +840,7 @@ class WebSocketProxyService extends ProxyService { () => _lookupPackageUris(isolateId, uris), ); - Future _lookupPackageUris( - String isolateId, - List uris, - ) async { + Future _lookupPackageUris(String _, List uris) async { await isInitialized; return UriList(uris: uris.map(DartUri.toPackageUri).toList()); } @@ -883,16 +872,9 @@ class WebSocketProxyService extends ProxyService { String isolateId, { String? idZoneId, int? limit, - }) => wrapInErrorHandlerAsync( - 'getStack', - () => _getStack(isolateId, idZoneId: idZoneId, limit: limit), - ); + }) => wrapInErrorHandlerAsync('getStack', () => _getStack(isolateId)); - Future _getStack( - String isolateId, { - String? idZoneId, - int? limit, - }) async { + Future _getStack(String isolateId) async { if (!_isIsolateRunning || _isolateRef == null) { throw vm_service.RPCError( 'getStack', From 3e919ce03d1888e5a18ad209296170bbf70ccb48 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 14:26:16 -0400 Subject: [PATCH 29/33] removed unused variable --- dwds/lib/src/services/web_socket_proxy_service.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dwds/lib/src/services/web_socket_proxy_service.dart b/dwds/lib/src/services/web_socket_proxy_service.dart index c0f07276e..4d5503e7c 100644 --- a/dwds/lib/src/services/web_socket_proxy_service.dart +++ b/dwds/lib/src/services/web_socket_proxy_service.dart @@ -556,15 +556,11 @@ class WebSocketProxyService extends ProxyService { Map? args, }) => wrapInErrorHandlerAsync( 'callServiceExtension', - () => _callServiceExtension(method, isolateId: isolateId, args: args), + () => _callServiceExtension(method, args: args), ); /// Calls a service extension on the client. - Future _callServiceExtension( - String method, { - String? isolateId, - Map? args, - }) async { + Future _callServiceExtension(String method, {Map? args}) async { final requestId = createId(); // Check if there's already a pending service extension with this ID From cd5b81095dd9d965cfd60931263c1a58731a13bf Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 14:32:37 -0400 Subject: [PATCH 30/33] updated port to 44456 --- dwds/lib/src/services/debug_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index 790d48c07..3de87cf79 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -419,7 +419,7 @@ class WebSocketDebugService implements IDebugService { webSocketProxyService, ); - final server = await startHttpServer(hostname, port: 0); + final server = await startHttpServer(hostname, port: 44456); serveHttpRequests(server, handler, (e, s) { Logger('WebSocketDebugService').warning('Error serving requests', e); }); From 1c55263fee3f13387b61dc5c5a613c118c8d2e3e Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 14:58:23 -0400 Subject: [PATCH 31/33] refactored classes to and interface names to be more clear --- dwds/lib/src/dwds_vm_client.dart | 22 ++++++----- dwds/lib/src/handlers/dev_handler.dart | 38 ++++++++++--------- dwds/lib/src/services/app_debug_services.dart | 22 +++++------ .../src/services/chrome_proxy_service.dart | 2 +- dwds/lib/src/services/debug_service.dart | 12 +++--- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index 030785913..6acd58cd7 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -39,7 +39,7 @@ enum _NamespacedServiceExtension { } /// Common interface for DWDS VM clients. -abstract class IDwdsVmClient { +abstract class DwdsVmClient { /// The VM service client. VmService get client; @@ -52,7 +52,7 @@ final _chromeLogger = Logger('DwdsVmClient'); // A client of the vm service that registers some custom extensions like // hotRestart. -class DwdsVmClient implements IDwdsVmClient { +class ChromeDwdsVmClient implements DwdsVmClient { @override final VmService client; final StreamController> _requestController; @@ -66,7 +66,11 @@ class DwdsVmClient implements IDwdsVmClient { /// Synchronizes hot restarts to avoid races. final _hotRestartQueue = AtomicQueue(); - DwdsVmClient(this.client, this._requestController, this._responseController); + ChromeDwdsVmClient( + this.client, + this._requestController, + this._responseController, + ); @override Future close() => @@ -76,8 +80,8 @@ class DwdsVmClient implements IDwdsVmClient { await client.dispose(); }(); - static Future create( - DebugService debugService, + static Future create( + ChromeDebugService debugService, DwdsStats dwdsStats, Uri? ddsUri, ) async { @@ -118,7 +122,7 @@ class DwdsVmClient implements IDwdsVmClient { clientCompleter.complete(client); } - final dwdsVmClient = DwdsVmClient( + final dwdsVmClient = ChromeDwdsVmClient( client, requestController, responseController, @@ -176,7 +180,7 @@ class DwdsVmClient implements IDwdsVmClient { static void _setUpVmServerConnection({ required ChromeProxyService chromeProxyService, required DwdsStats dwdsStats, - required DebugService debugService, + required ChromeDebugService debugService, required Stream responseStream, required StreamSink responseSink, required Stream requestStream, @@ -276,7 +280,7 @@ class DwdsVmClient implements IDwdsVmClient { static Future _registerServiceExtensions({ required VmService client, required ChromeProxyService chromeProxyService, - required DwdsVmClient dwdsVmClient, + required ChromeDwdsVmClient dwdsVmClient, }) async { client.registerServiceCallback( 'hotRestart', @@ -309,7 +313,7 @@ class DwdsVmClient implements IDwdsVmClient { final _webSocketLogger = Logger('WebSocketDwdsVmClient'); /// WebSocket-based DWDS VM client. -class WebSocketDwdsVmClient implements IDwdsVmClient { +class WebSocketDwdsVmClient implements DwdsVmClient { @override final VmService client; final StreamController _requestController; diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index cd656678d..c93e16719 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -59,7 +59,7 @@ class DevHandler { final AssetReader _assetReader; final String _hostname; final _connectedApps = StreamController.broadcast(); - final _servicesByAppId = {}; + final _servicesByAppId = {}; final _appConnectionByAppId = {}; final Stream buildResults; final Future Function() _chromeConnection; @@ -163,8 +163,8 @@ class DevHandler { return successfulSends; } - /// Starts a [DebugService] for local debugging. - Future _startLocalDebugService( + /// Starts a [ChromeDebugService] for local debugging. + Future _startLocalDebugService( ChromeConnection chromeConnection, AppConnection appConnection, ) async { @@ -224,7 +224,7 @@ class DevHandler { final webkitDebugger = WebkitDebugger(WipDebugger(tabConnection)); - return DebugService.start( + return ChromeDebugService.start( // We assume the user will connect to the debug service on the same // machine. This allows consumers of DWDS to provide a `hostname` for // debugging through the Dart Debug Extension without impacting the local @@ -264,7 +264,7 @@ class DevHandler { ); } - Future loadAppServices(AppConnection appConnection) async { + Future loadAppServices(AppConnection appConnection) async { final appId = appConnection.request.appId; var appServices = _servicesByAppId[appId]; if (appServices == null) { @@ -281,7 +281,7 @@ class DevHandler { } /// Creates WebSocket-based app services for hot reload functionality. - Future _createWebSocketAppServices( + Future _createWebSocketAppServices( AppConnection appConnection, ) async { final webSocketDebugService = await WebSocketDebugService.start( @@ -296,7 +296,7 @@ class DevHandler { } /// Creates Chrome-based app services for full debugging capabilities. - Future _createChromeAppServices( + Future _createChromeAppServices( AppConnection appConnection, ) async { final debugService = await _startLocalDebugService( @@ -308,7 +308,7 @@ class DevHandler { /// Sets up cleanup handling for Chrome-based services. void _setupChromeServiceCleanup( - IAppDebugServices appServices, + AppDebugServices appServices, AppConnection appConnection, ) { final chromeProxy = appServices.proxyService; @@ -468,7 +468,7 @@ class DevHandler { return; } final debuggerStart = DateTime.now(); - IAppDebugServices appServices; + AppDebugServices appServices; try { appServices = await loadAppServices(appConnection); } catch (_) { @@ -733,7 +733,7 @@ class DevHandler { /// Handles reconnection to existing services for web-socket mode. Future _reconnectToService( - IAppDebugServices services, + AppDebugServices services, AppConnection? existingConnection, AppConnection newConnection, ConnectRequest message, @@ -846,8 +846,8 @@ class DevHandler { ); } - Future _createAppDebugServices( - DebugService debugService, + Future _createAppDebugServices( + ChromeDebugService debugService, ) async { final dwdsStats = DwdsStats(); Uri? ddsUri; @@ -855,8 +855,12 @@ class DevHandler { final dds = await debugService.startDartDevelopmentService(); ddsUri = dds.wsUri; } - final vmClient = await DwdsVmClient.create(debugService, dwdsStats, ddsUri); - final appDebugService = AppDebugServices( + final vmClient = await ChromeDwdsVmClient.create( + debugService, + dwdsStats, + ddsUri, + ); + final appDebugService = ChromeAppDebugServices( debugService, vmClient, dwdsStats, @@ -883,7 +887,7 @@ class DevHandler { return appDebugService; } - Future _createAppDebugServicesWebSocketMode( + Future _createAppDebugServicesWebSocketMode( WebSocketDebugService webSocketDebugService, AppConnection appConnection, ) async { @@ -918,7 +922,7 @@ class DevHandler { } } - /// Starts a [DebugService] for Dart Debug Extension. + /// Starts a [ChromeProxyService] for Dart Debug Extension. void _startExtensionDebugService(ExtensionDebugger extensionDebugger) { // Waits for a `DevToolsRequest` to be sent from the extension background // when the extension is clicked. @@ -965,7 +969,7 @@ class DevHandler { final debuggerStart = DateTime.now(); var appServices = _servicesByAppId[appId]; if (appServices == null) { - final debugService = await DebugService.start( + final debugService = await ChromeDebugService.start( _hostname, extensionDebugger, executionContext, diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index 122f5cb9a..973c6f2c1 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -10,9 +10,9 @@ import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/proxy_service.dart'; /// Common interface for debug service containers. -abstract class IAppDebugServices { - IDebugService get debugService; - IDwdsVmClient get dwdsVmClient; +abstract class AppDebugServices { + DebugService get debugService; + DwdsVmClient get dwdsVmClient; DwdsStats? get dwdsStats; Uri? get ddsUri; String? get connectedInstanceId; @@ -22,15 +22,15 @@ abstract class IAppDebugServices { } /// Chrome-based debug services container. -class AppDebugServices implements IAppDebugServices { - final DebugService _debugService; - final DwdsVmClient _dwdsVmClient; +class ChromeAppDebugServices implements AppDebugServices { + final ChromeDebugService _debugService; + final ChromeDwdsVmClient _dwdsVmClient; final DwdsStats _dwdsStats; final Uri? _ddsUri; Future? _closed; String? _connectedInstanceId; - AppDebugServices( + ChromeAppDebugServices( this._debugService, this._dwdsVmClient, this._dwdsStats, @@ -38,10 +38,10 @@ class AppDebugServices implements IAppDebugServices { ); @override - DebugService get debugService => _debugService; + ChromeDebugService get debugService => _debugService; @override - IDwdsVmClient get dwdsVmClient => _dwdsVmClient; + DwdsVmClient get dwdsVmClient => _dwdsVmClient; @override DwdsStats get dwdsStats => _dwdsStats; @@ -65,7 +65,7 @@ class AppDebugServices implements IAppDebugServices { } /// WebSocket-based implementation of app debug services. -class WebSocketAppDebugServices implements IAppDebugServices { +class WebSocketAppDebugServices implements AppDebugServices { final WebSocketDebugService _debugService; final WebSocketDwdsVmClient _dwdsVmClient; Future? _closed; @@ -77,7 +77,7 @@ class WebSocketAppDebugServices implements IAppDebugServices { WebSocketDebugService get debugService => _debugService; @override - IDwdsVmClient get dwdsVmClient => _dwdsVmClient; + DwdsVmClient get dwdsVmClient => _dwdsVmClient; @override String? get connectedInstanceId => _connectedInstanceId; diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index faba02e59..27080471c 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -1548,7 +1548,7 @@ class ChromeProxyService extends ProxyService { @override Future yieldControlToDDS(String uri) async { - final canYield = DebugService.yieldControlToDDS(uri); + final canYield = ChromeDebugService.yieldControlToDDS(uri); if (!canYield) { throw RPCError( diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index 3de87cf79..d13f25ab9 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -128,7 +128,7 @@ Future _handleSseConnections( } /// Common interface for debug services (Chrome or WebSocket based). -abstract class IDebugService { +abstract class DebugService { String get hostname; int get port; String get uri; @@ -140,7 +140,7 @@ abstract class IDebugService { /// A Dart Web Debug Service. /// /// Creates a [ChromeProxyService] from an existing Chrome instance. -class DebugService implements IDebugService { +class ChromeDebugService implements DebugService { static String? _ddsUri; final VmServiceInterface chromeProxyService; @@ -163,7 +163,7 @@ class DebugService implements IDebugService { /// All subsequent calls to [close] will return this future. Future? _closed; - DebugService._( + ChromeDebugService._( this.chromeProxyService, this.hostname, this.port, @@ -235,7 +235,7 @@ class DebugService implements IDebugService { return true; } - static Future start( + static Future start( String hostname, RemoteDebugger remoteDebugger, ExecutionContext executionContext, @@ -305,7 +305,7 @@ class DebugService implements IDebugService { _logger.warning('Error serving requests', e); emitEvent(DwdsEvent.httpRequestException('DebugService', '$e:$s')); }); - return DebugService._( + return ChromeDebugService._( chromeProxyService, server.address.host, server.port, @@ -325,7 +325,7 @@ class DebugService implements IDebugService { typedef SendClientRequest = int Function(Object request); /// WebSocket-based debug service for web debugging. -class WebSocketDebugService implements IDebugService { +class WebSocketDebugService implements DebugService { @override final String hostname; @override From 956c32333abba64e4c52d7661476b5a1c7a0819d Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Wed, 23 Jul 2025 15:02:51 -0400 Subject: [PATCH 32/33] fix variable name error --- dwds/lib/src/connections/debug_connection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart index 2d37cd5d0..06efd932c 100644 --- a/dwds/lib/src/connections/debug_connection.dart +++ b/dwds/lib/src/connections/debug_connection.dart @@ -13,7 +13,7 @@ import 'package:vm_service/vm_service.dart'; /// Supports debugging your running application through the Dart VM Service /// Protocol. class DebugConnection { - final IAppDebugServices _appDebugServices; + final AppDebugServices _appDebugServices; final _onDoneCompleter = Completer(); /// Null until [close] is called. From fb5057bb48c201a425ef6cf2ac24b757e60cb55e Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Thu, 24 Jul 2025 13:14:50 -0400 Subject: [PATCH 33/33] updated variable to be type --- dwds/lib/src/dwds_vm_client.dart | 3 +-- dwds/lib/src/services/app_debug_services.dart | 14 +++----------- dwds/lib/src/services/debug_service.dart | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index 6acd58cd7..815e51e3d 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -85,8 +85,7 @@ class ChromeDwdsVmClient implements DwdsVmClient { DwdsStats dwdsStats, Uri? ddsUri, ) async { - final chromeProxyService = - debugService.chromeProxyService as ChromeProxyService; + final chromeProxyService = debugService.chromeProxyService; final responseController = StreamController(); final responseSink = responseController.sink; // Response stream must be a broadcast stream so that it can have multiple diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart index 973c6f2c1..4d262281b 100644 --- a/dwds/lib/src/services/app_debug_services.dart +++ b/dwds/lib/src/services/app_debug_services.dart @@ -4,8 +4,6 @@ import 'package:dwds/src/dwds_vm_client.dart'; import 'package:dwds/src/events.dart'; -import 'package:dwds/src/services/chrome_proxy_service.dart' - show ChromeProxyService; import 'package:dwds/src/services/debug_service.dart'; import 'package:dwds/src/services/proxy_service.dart'; @@ -56,8 +54,7 @@ class ChromeAppDebugServices implements AppDebugServices { set connectedInstanceId(String? id) => _connectedInstanceId = id; @override - ProxyService get proxyService => - debugService.chromeProxyService as ChromeProxyService; + ProxyService get proxyService => debugService.chromeProxyService; @override Future close() => @@ -69,7 +66,8 @@ class WebSocketAppDebugServices implements AppDebugServices { final WebSocketDebugService _debugService; final WebSocketDwdsVmClient _dwdsVmClient; Future? _closed; - String? _connectedInstanceId; + @override + String? connectedInstanceId; WebSocketAppDebugServices(this._debugService, this._dwdsVmClient); @@ -79,12 +77,6 @@ class WebSocketAppDebugServices implements AppDebugServices { @override DwdsVmClient get dwdsVmClient => _dwdsVmClient; - @override - String? get connectedInstanceId => _connectedInstanceId; - - @override - set connectedInstanceId(String? id) => _connectedInstanceId = id; - // WebSocket-only service - Chrome/DDS features not available @override DwdsStats? get dwdsStats => null; diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index d13f25ab9..a9c2537e2 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -143,7 +143,7 @@ abstract class DebugService { class ChromeDebugService implements DebugService { static String? _ddsUri; - final VmServiceInterface chromeProxyService; + final ChromeProxyService chromeProxyService; @override final String hostname; @override