Skip to content

Commit a4e0dd0

Browse files
committed
simulate debugExtension in websocket-based execution flow
1 parent 489dbcc commit a4e0dd0

File tree

3 files changed

+156
-64
lines changed

3 files changed

+156
-64
lines changed

dwds/lib/dart_web_debug_service.dart

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -62,47 +62,10 @@ class Dwds {
6262
Future<DebugConnection> debugConnection(AppConnection appConnection) async {
6363
if (!_enableDebugging) throw StateError('Debugging is not enabled.');
6464

65-
final appDebugServices = await _devHandler.loadAppServices(appConnection);
66-
67-
// Initialize the appropriate proxy service based on connection type
6865
if (_useDwdsWebSocketConnection) {
69-
await _initializeWebSocketService(appDebugServices);
66+
return await _devHandler.createDebugConnectionForWebSocket(appConnection);
7067
} else {
71-
await _initializeChromeService(appDebugServices);
72-
}
73-
74-
return DebugConnection(appDebugServices);
75-
}
76-
77-
/// Initializes and waits for WebSocket proxy service to be ready.
78-
Future<void> _initializeWebSocketService(dynamic appDebugServices) async {
79-
try {
80-
final webSocketProxyService = appDebugServices.webSocketProxyService;
81-
if (webSocketProxyService != null) {
82-
await webSocketProxyService.isInitialized;
83-
_logger.fine('WebSocket proxy service initialized successfully');
84-
} else {
85-
_logger.warning('WebSocket proxy service is null');
86-
}
87-
} catch (e) {
88-
_logger.severe('Failed to initialize WebSocket proxy service: $e');
89-
rethrow;
90-
}
91-
}
92-
93-
/// Initializes and waits for Chrome proxy service to be ready.
94-
Future<void> _initializeChromeService(dynamic appDebugServices) async {
95-
try {
96-
final chromeProxyService = appDebugServices.chromeProxyService;
97-
if (chromeProxyService != null) {
98-
await chromeProxyService.isInitialized;
99-
_logger.fine('Chrome proxy service initialized successfully');
100-
} else {
101-
_logger.warning('Chrome proxy service is null');
102-
}
103-
} catch (e) {
104-
_logger.severe('Failed to initialize Chrome proxy service: $e');
105-
rethrow;
68+
return await _devHandler.createDebugConnectionForChrome(appConnection);
10669
}
10770
}
10871

dwds/lib/src/handlers/dev_handler.dart

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -403,20 +403,25 @@ class DevHandler {
403403
) async {
404404
if (message == null) return;
405405

406+
final appId = connection.request.appId;
407+
final wsService = _servicesByAppId[appId]?.webSocketProxyService;
408+
409+
if (wsService == null) {
410+
_logger.warning(
411+
'No WebSocketProxyService found for appId: $appId to process $message',
412+
);
413+
return;
414+
}
406415
if (message is HotReloadResponse) {
407-
_servicesByAppId[connection.request.appId]?.webSocketProxyService
408-
?.completeHotReload(message);
416+
wsService.completeHotReload(message);
409417
} else if (message is ServiceExtensionResponse) {
410-
final appId = connection.request.appId;
411-
final wsService = _servicesByAppId[appId]?.webSocketProxyService;
412-
413-
if (wsService != null) {
414-
wsService.completeServiceExtension(message);
415-
} else {
416-
_logger.warning(
417-
'No WebSocketProxyService found for appId: $appId to complete service extension',
418-
);
419-
}
418+
wsService.completeServiceExtension(message);
419+
} else if (message is RegisterEvent) {
420+
wsService.parseRegisterEvent(message);
421+
} else if (message is BatchedDebugEvents) {
422+
wsService.parseBatchedDebugEvents(message);
423+
} else if (message is DebugEvent) {
424+
wsService.parseDebugEvent(message);
420425
} else {
421426
throw UnsupportedError(
422427
'Message type ${message.runtimeType} is not supported in WebSocket mode',
@@ -559,6 +564,47 @@ class DevHandler {
559564
);
560565
}
561566

567+
/// Creates a debug connection for WebSocket mode.
568+
Future<DebugConnection> createDebugConnectionForWebSocket(
569+
AppConnection appConnection,
570+
) async {
571+
final appDebugServices = await loadAppServices(appConnection);
572+
573+
// Initialize WebSocket proxy service
574+
final webSocketProxyService = appDebugServices.webSocketProxyService;
575+
if (webSocketProxyService != null) {
576+
await webSocketProxyService.isInitialized;
577+
_logger.fine('WebSocket proxy service initialized successfully');
578+
} else {
579+
_logger.warning('WebSocket proxy service is null');
580+
}
581+
582+
return DebugConnection(appDebugServices);
583+
}
584+
585+
/// Creates a debug connection for Chrome mode.
586+
Future<DebugConnection> createDebugConnectionForChrome(
587+
AppConnection appConnection,
588+
) async {
589+
final appDebugServices = await loadAppServices(appConnection);
590+
591+
// Initialize Chrome proxy service
592+
try {
593+
final chromeProxyService = appDebugServices.chromeProxyService;
594+
if (chromeProxyService != null) {
595+
await chromeProxyService.isInitialized;
596+
_logger.fine('Chrome proxy service initialized successfully');
597+
} else {
598+
_logger.warning('Chrome proxy service is null');
599+
}
600+
} catch (e) {
601+
_logger.severe('Failed to initialize Chrome proxy service: $e');
602+
rethrow;
603+
}
604+
605+
return DebugConnection(appDebugServices);
606+
}
607+
562608
/// Handles connection requests for both Chrome and WebSocket modes.
563609
Future<AppConnection> _handleConnectRequest(
564610
ConnectRequest message,
@@ -600,9 +646,30 @@ class DevHandler {
600646
isWebSocketMode,
601647
);
602648
} else {
649+
// Complete the readyToRunMainCompleter immediately since we're
650+
// creating a new connection.
603651
// If this is the initial app connection, we can run the app's main()
604652
// method immediately.
605653
readyToRunMainCompleter.complete();
654+
655+
// For WebSocket mode, we need to proactively create and emit a debug connection
656+
// since Flutter tools won't call debugConnection() for WebServerDevice
657+
if (isWebSocketMode) {
658+
try {
659+
// This will call loadAppServices() and initialize the WebSocket service
660+
final debugConnection = await createDebugConnectionForWebSocket(
661+
connection,
662+
);
663+
664+
// Emit the debug connection through the extension stream
665+
// This should trigger Flutter tools to pick it up as if it was an extension connection
666+
extensionDebugConnections.add(debugConnection);
667+
} catch (e, s) {
668+
_logger.warning(
669+
'Failed to create WebSocket debug connection: $e\n$s',
670+
);
671+
}
672+
}
606673
}
607674
_appConnectionByAppId[message.appId] = connection;
608675
_connectedApps.add(connection);

dwds/lib/src/services/web_socket_proxy_service.dart

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert';
67

8+
import 'package:dwds/data/debug_event.dart';
79
import 'package:dwds/data/hot_reload_request.dart';
810
import 'package:dwds/data/hot_reload_response.dart';
11+
import 'package:dwds/data/register_event.dart';
912
import 'package:dwds/data/service_extension_request.dart';
1013
import 'package:dwds/data/service_extension_response.dart';
1114
import 'package:dwds/src/connections/app_connection.dart';
@@ -126,18 +129,20 @@ class WebSocketProxyService implements VmServiceInterface {
126129
if (!_initializedCompleter.isCompleted) _initializedCompleter.complete();
127130

128131
// Set up appConnection.onStart listener (like Chrome flow does)
129-
appConn.onStart.then((_) {
130-
// Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume
131-
if (pauseIsolatesOnStart && !_hasResumed) {
132-
final resumeEvent = vm_service.Event(
133-
kind: vm_service.EventKind.kResume,
134-
timestamp: DateTime.now().millisecondsSinceEpoch,
135-
isolate: isolateRef,
136-
);
137-
_hasResumed = true;
138-
_streamNotify(vm_service.EventStreams.kDebug, resumeEvent);
139-
}
140-
});
132+
safeUnawaited(
133+
appConn.onStart.then((_) {
134+
// Unlike Chrome flow, we don't have debugger.resumeFromStart(), but we can trigger resume
135+
if (pauseIsolatesOnStart && !_hasResumed) {
136+
final resumeEvent = vm_service.Event(
137+
kind: vm_service.EventKind.kResume,
138+
timestamp: DateTime.now().millisecondsSinceEpoch,
139+
isolate: isolateRef,
140+
);
141+
_hasResumed = true;
142+
_streamNotify(vm_service.EventStreams.kDebug, resumeEvent);
143+
}
144+
}),
145+
);
141146

142147
// Send pause event if enabled
143148
if (pauseIsolatesOnStart) {
@@ -523,6 +528,63 @@ class WebSocketProxyService implements VmServiceInterface {
523528
}
524529
}
525530

531+
/// Parses the [RegisterEvent] and emits a corresponding Dart VM Service
532+
/// protocol [Event].
533+
void parseRegisterEvent(RegisterEvent registerEvent) {
534+
_logger.fine('Parsing RegisterEvent: ${registerEvent.eventData}');
535+
536+
if (!_isIsolateRunning || _isolateRef == null) {
537+
_logger.warning('Cannot register service extension - no isolate running');
538+
return;
539+
}
540+
541+
final service = registerEvent.eventData;
542+
543+
// Add the service to the isolate's extension RPCs if we had access to the isolate
544+
// In WebSocket mode, we don't maintain the full isolate object like Chrome mode,
545+
// but we can still emit the ServiceExtensionAdded event for tooling
546+
547+
final event = vm_service.Event(
548+
kind: vm_service.EventKind.kServiceExtensionAdded,
549+
timestamp: DateTime.now().millisecondsSinceEpoch,
550+
isolate: _isolateRef!,
551+
);
552+
event.extensionRPC = service;
553+
554+
_streamNotify(vm_service.EventStreams.kIsolate, event);
555+
_logger.fine('Emitted ServiceExtensionAdded event for: $service');
556+
}
557+
558+
/// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service
559+
/// protocol [Event]s.
560+
void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) {
561+
for (final debugEvent in debugEvents.events) {
562+
parseDebugEvent(debugEvent);
563+
}
564+
}
565+
566+
/// Parses the [DebugEvent] and emits a corresponding Dart VM Service
567+
/// protocol [Event].
568+
void parseDebugEvent(DebugEvent debugEvent) {
569+
if (!_isIsolateRunning || _isolateRef == null) {
570+
_logger.warning('Cannot parse debug event - no isolate running');
571+
return;
572+
}
573+
574+
_streamNotify(
575+
vm_service.EventStreams.kExtension,
576+
vm_service.Event(
577+
kind: vm_service.EventKind.kExtension,
578+
timestamp: DateTime.now().millisecondsSinceEpoch,
579+
isolate: _isolateRef!,
580+
)
581+
..extensionKind = debugEvent.kind
582+
..extensionData = vm_service.ExtensionData.parse(
583+
jsonDecode(debugEvent.eventData) as Map<String, dynamic>,
584+
),
585+
);
586+
}
587+
526588
@override
527589
Future<Success> setFlag(String name, String value) =>
528590
wrapInErrorHandlerAsync('setFlag', () => _setFlag(name, value));
@@ -608,7 +670,7 @@ class WebSocketProxyService implements VmServiceInterface {
608670
if (!_hasResumed && _currentPauseEvent != null) {
609671
appConnection.runMain();
610672
}
611-
673+
612674
// Prevent multiple resume calls
613675
if (_hasResumed && _currentPauseEvent == null) {
614676
return Success();

0 commit comments

Comments
 (0)