Skip to content

Commit 813765e

Browse files
committed
consolidate dwdsVmClient and WebSocketDwdsVmClient
1 parent a61bde1 commit 813765e

File tree

4 files changed

+222
-220
lines changed

4 files changed

+222
-220
lines changed

dwds/lib/src/dwds_vm_client.dart

Lines changed: 219 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:dwds/src/events.dart';
99
import 'package:dwds/src/services/chrome_debug_exception.dart';
1010
import 'package:dwds/src/services/chrome_proxy_service.dart';
1111
import 'package:dwds/src/services/debug_service.dart';
12+
import 'package:dwds/src/services/web_socket_proxy_service.dart';
1213
import 'package:dwds/src/utilities/synchronized.dart';
1314
import 'package:logging/logging.dart';
1415
import 'package:uuid/uuid.dart';
@@ -17,8 +18,6 @@ import 'package:vm_service/vm_service_io.dart';
1718
import 'package:vm_service_interface/vm_service_interface.dart';
1819
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
1920

20-
final _logger = Logger('DwdsVmClient');
21-
2221
/// Type of requests added to the request controller.
2322
typedef VmRequest = Map<String, Object>;
2423

@@ -38,9 +37,22 @@ enum _NamespacedServiceExtension {
3837
final String method;
3938
}
4039

40+
/// Common interface for DWDS VM clients.
41+
abstract class IDwdsVmClient {
42+
/// The VM service client.
43+
VmService get client;
44+
45+
/// Closes the VM client and releases resources.
46+
Future<void> close();
47+
}
48+
49+
// Chrome-based DWDS VM client logger.
50+
final _chromeLogger = Logger('DwdsVmClient');
51+
4152
// A client of the vm service that registers some custom extensions like
4253
// hotRestart.
43-
class DwdsVmClient {
54+
class DwdsVmClient implements IDwdsVmClient {
55+
@override
4456
final VmService client;
4557
final StreamController<Map<String, Object>> _requestController;
4658
final StreamController<Map<String, Object?>> _responseController;
@@ -137,7 +149,7 @@ class DwdsVmClient {
137149
}) {
138150
final client = VmService(responseStream.map(jsonEncode), (request) {
139151
if (requestController.isClosed) {
140-
_logger.warning(
152+
_chromeLogger.warning(
141153
'Attempted to send a request but the connection is closed:\n\n'
142154
'$request',
143155
);
@@ -331,13 +343,13 @@ void _processSendEvent(Map<String, dynamic> request, DwdsStats dwdsStats) {
331343
switch (type) {
332344
case 'DevtoolsEvent':
333345
{
334-
_logger.finest('Received DevTools event: $event');
346+
_chromeLogger.finest('Received DevTools event: $event');
335347
final action = payload?['action'] as String?;
336348
final screen = payload?['screen'] as String?;
337349
if (screen != null && action == 'pageReady') {
338350
_recordDwdsStats(dwdsStats, screen);
339351
} else {
340-
_logger.finest('Ignoring unknown event: $event');
352+
_chromeLogger.finest('Ignoring unknown event: $event');
341353
}
342354
}
343355
}
@@ -351,16 +363,16 @@ void _recordDwdsStats(DwdsStats dwdsStats, String screen) {
351363
final devToolLoadTime =
352364
DateTime.now().difference(devToolsStart).inMilliseconds;
353365
emitEvent(DwdsEvent.devToolsLoad(devToolLoadTime, screen));
354-
_logger.fine('DevTools load time: $devToolLoadTime ms');
366+
_chromeLogger.fine('DevTools load time: $devToolLoadTime ms');
355367
}
356368
if (debuggerStart != null) {
357369
final debuggerReadyTime =
358370
DateTime.now().difference(debuggerStart).inMilliseconds;
359371
emitEvent(DwdsEvent.debuggerReady(debuggerReadyTime, screen));
360-
_logger.fine('Debugger ready time: $debuggerReadyTime ms');
372+
_chromeLogger.fine('Debugger ready time: $debuggerReadyTime ms');
361373
}
362374
} else {
363-
_logger.finest('Debugger and DevTools stats are already recorded.');
375+
_chromeLogger.finest('Debugger and DevTools stats are already recorded.');
364376
}
365377
}
366378

@@ -381,14 +393,14 @@ Future<Map<String, dynamic>> _hotRestart(
381393
ChromeProxyService chromeProxyService,
382394
VmService client,
383395
) async {
384-
_logger.info('Attempting a hot restart');
396+
_chromeLogger.info('Attempting a hot restart');
385397

386398
chromeProxyService.terminatingIsolates = true;
387399
await _disableBreakpointsAndResume(client, chromeProxyService);
388400
try {
389-
_logger.info('Attempting to get execution context ID.');
401+
_chromeLogger.info('Attempting to get execution context ID.');
390402
await tryGetContextId(chromeProxyService);
391-
_logger.info('Got execution context ID.');
403+
_chromeLogger.info('Got execution context ID.');
392404
} on StateError catch (e) {
393405
// We couldn't find the execution context. `hotRestart` may have been
394406
// triggered in the middle of a full reload.
@@ -408,12 +420,12 @@ Future<Map<String, dynamic>> _hotRestart(
408420
}
409421
// Generate run id to hot restart all apps loaded into the tab.
410422
final runId = const Uuid().v4().toString();
411-
_logger.info('Issuing \$dartHotRestartDwds request');
423+
_chromeLogger.info('Issuing \$dartHotRestartDwds request');
412424
await chromeProxyService.inspector.jsEvaluate(
413425
'\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);',
414426
awaitPromise: true,
415427
);
416-
_logger.info('\$dartHotRestartDwds request complete.');
428+
_chromeLogger.info('\$dartHotRestartDwds request complete.');
417429
} on WipError catch (exception) {
418430
final code = exception.error?['code'];
419431
final message = exception.error?['message'];
@@ -433,11 +445,11 @@ Future<Map<String, dynamic>> _hotRestart(
433445
},
434446
};
435447
}
436-
_logger.info('Waiting for Isolate Start event.');
448+
_chromeLogger.info('Waiting for Isolate Start event.');
437449
await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
438450
chromeProxyService.terminatingIsolates = false;
439451

440-
_logger.info('Successful hot restart');
452+
_chromeLogger.info('Successful hot restart');
441453
return {'result': Success().toJson()};
442454
}
443455

@@ -455,18 +467,20 @@ void _waitForResumeEventToRunMain(ChromeProxyService chromeProxyService) {
455467
Future<Map<String, dynamic>> _fullReload(
456468
ChromeProxyService chromeProxyService,
457469
) async {
458-
_logger.info('Attempting a full reload');
470+
_chromeLogger.info('Attempting a full reload');
459471
await chromeProxyService.remoteDebugger.enablePage();
460472
await chromeProxyService.remoteDebugger.pageReload();
461-
_logger.info('Successful full reload');
473+
_chromeLogger.info('Successful full reload');
462474
return {'result': Success().toJson()};
463475
}
464476

465477
Future<void> _disableBreakpointsAndResume(
466478
VmService client,
467479
ChromeProxyService chromeProxyService,
468480
) async {
469-
_logger.info('Attempting to disable breakpoints and resume the isolate');
481+
_chromeLogger.info(
482+
'Attempting to disable breakpoints and resume the isolate',
483+
);
470484
final vm = await client.getVM();
471485
final isolates = vm.isolates;
472486
if (isolates == null || isolates.isEmpty) {
@@ -495,9 +509,193 @@ Future<void> _disableBreakpointsAndResume(
495509
await client.resume(isolateId);
496510
} on RPCError catch (e, s) {
497511
if (!e.message.contains('Can only perform operation while paused')) {
498-
_logger.severe('Hot restart failed to resume exiting isolate', e, s);
512+
_chromeLogger.severe(
513+
'Hot restart failed to resume exiting isolate',
514+
e,
515+
s,
516+
);
499517
rethrow;
500518
}
501519
}
502-
_logger.info('Successfully disabled breakpoints and resumed the isolate');
520+
_chromeLogger.info(
521+
'Successfully disabled breakpoints and resumed the isolate',
522+
);
523+
}
524+
525+
// WebSocket-based DWDS VM client logger.
526+
final _webSocketLogger = Logger('WebSocketDwdsVmClient');
527+
528+
/// WebSocket-based DWDS VM client.
529+
class WebSocketDwdsVmClient implements IDwdsVmClient {
530+
@override
531+
final VmService client;
532+
final StreamController<VmRequest> _requestController;
533+
final StreamController<VmResponse> _responseController;
534+
Future<void>? _closed;
535+
536+
WebSocketDwdsVmClient(
537+
this.client,
538+
this._requestController,
539+
this._responseController,
540+
);
541+
542+
@override
543+
Future<void> close() =>
544+
_closed ??= () async {
545+
await _requestController.close();
546+
await _responseController.close();
547+
await client.dispose();
548+
}();
549+
550+
static Future<WebSocketDwdsVmClient> create(
551+
WebSocketDebugService debugService,
552+
) async {
553+
_webSocketLogger.fine('Creating WebSocket DWDS VM client');
554+
final webSocketProxyService = debugService.webSocketProxyService;
555+
final responseController = StreamController<VmResponse>();
556+
final responseSink = responseController.sink;
557+
final responseStream = responseController.stream.asBroadcastStream();
558+
final requestController = StreamController<VmRequest>();
559+
final requestSink = requestController.sink;
560+
final requestStream = requestController.stream;
561+
562+
_setUpWebSocketVmServerConnection(
563+
webSocketProxyService: webSocketProxyService,
564+
debugService: debugService,
565+
responseStream: responseStream,
566+
responseSink: responseSink,
567+
requestStream: requestStream,
568+
requestSink: requestSink,
569+
);
570+
571+
final client = _setUpWebSocketVmClient(
572+
responseStream: responseStream,
573+
requestController: requestController,
574+
requestSink: requestSink,
575+
);
576+
577+
_webSocketLogger.fine('WebSocket DWDS VM client created successfully');
578+
return WebSocketDwdsVmClient(client, requestController, responseController);
579+
}
580+
581+
static VmService _setUpWebSocketVmClient({
582+
required Stream<VmResponse> responseStream,
583+
required StreamSink<VmRequest> requestSink,
584+
required StreamController<VmRequest> requestController,
585+
}) {
586+
final client = VmService(responseStream.map(jsonEncode), (request) {
587+
if (requestController.isClosed) {
588+
_webSocketLogger.warning(
589+
'Attempted to send a request but the connection is closed:\n\n$request',
590+
);
591+
return;
592+
}
593+
requestSink.add(Map<String, Object>.from(jsonDecode(request)));
594+
});
595+
return client;
596+
}
597+
598+
static void _setUpWebSocketVmServerConnection({
599+
required WebSocketProxyService webSocketProxyService,
600+
required WebSocketDebugService debugService,
601+
required Stream<VmResponse> responseStream,
602+
required StreamSink<VmResponse> responseSink,
603+
required Stream<VmRequest> requestStream,
604+
required StreamSink<VmRequest> requestSink,
605+
}) {
606+
responseStream.listen((request) async {
607+
final response = await _maybeHandleWebSocketServiceExtensionRequest(
608+
request,
609+
webSocketProxyService: webSocketProxyService,
610+
);
611+
if (response != null) {
612+
requestSink.add(response);
613+
}
614+
});
615+
616+
final vmServerConnection = VmServerConnection(
617+
requestStream,
618+
responseSink,
619+
debugService.serviceExtensionRegistry,
620+
webSocketProxyService,
621+
);
622+
623+
// Register service extensions
624+
for (final extension in _NamespacedServiceExtension.values) {
625+
_webSocketLogger.finest(
626+
'Registering service extension: ${extension.method}',
627+
);
628+
debugService.serviceExtensionRegistry.registerExtension(
629+
extension.method,
630+
vmServerConnection,
631+
);
632+
}
633+
}
634+
635+
static Future<VmRequest?> _maybeHandleWebSocketServiceExtensionRequest(
636+
VmResponse request, {
637+
required WebSocketProxyService webSocketProxyService,
638+
}) async {
639+
VmRequest? response;
640+
final method = request['method'];
641+
642+
_webSocketLogger.finest('Processing service extension method: $method');
643+
644+
if (method == _NamespacedServiceExtension.flutterListViews.method) {
645+
response = await _webSocketFlutterListViewsHandler(webSocketProxyService);
646+
} else if (method == _NamespacedServiceExtension.extDwdsEmitEvent.method) {
647+
response = _webSocketExtDwdsEmitEventHandler(request);
648+
} else if (method == _NamespacedServiceExtension.extDwdsReload.method) {
649+
response = {'result': 'Reload not implemented'};
650+
} else if (method == _NamespacedServiceExtension.extDwdsSendEvent.method) {
651+
response = _webSocketExtDwdsSendEventHandler(request);
652+
} else if (method == _NamespacedServiceExtension.extDwdsScreenshot.method) {
653+
response = {'result': 'Screenshot not implemented'};
654+
}
655+
656+
if (response != null) {
657+
response['id'] = request['id'] as String;
658+
response['jsonrpc'] = '2.0';
659+
}
660+
return response;
661+
}
662+
663+
static Future<Map<String, Object>> _webSocketFlutterListViewsHandler(
664+
WebSocketProxyService webSocketProxyService,
665+
) async {
666+
final vm = await webSocketProxyService.getVM();
667+
_webSocketLogger.finest(
668+
'Retrieved VM with ${vm.isolates?.length ?? 0} isolates',
669+
);
670+
final isolates = vm.isolates;
671+
return <String, Object>{
672+
'result': <String, Object>{
673+
'views': <Object>[
674+
for (final isolate in isolates ?? [])
675+
<String, Object>{'id': isolate.id, 'isolate': isolate.toJson()},
676+
],
677+
},
678+
};
679+
}
680+
681+
static Map<String, Object> _webSocketExtDwdsEmitEventHandler(
682+
VmResponse request,
683+
) {
684+
final event = request['params'] as Map<String, dynamic>?;
685+
if (event != null) {
686+
final type = event['type'] as String?;
687+
final payload = event['payload'] as Map<String, dynamic>?;
688+
if (type != null && payload != null) {
689+
_webSocketLogger.fine('EmitEvent: $type $payload');
690+
}
691+
}
692+
return {'result': 'EmitEvent handled'};
693+
}
694+
695+
static Map<String, Object> _webSocketExtDwdsSendEventHandler(
696+
VmResponse request,
697+
) {
698+
_webSocketLogger.fine('SendEvent: $request');
699+
return {'result': 'SendEvent handled'};
700+
}
503701
}

dwds/lib/src/handlers/dev_handler.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import 'package:dwds/src/services/debug_service.dart';
3636
import 'package:dwds/src/services/expression_compiler.dart';
3737
import 'package:dwds/src/services/web_socket_proxy_service.dart';
3838
import 'package:dwds/src/utilities/shared.dart';
39-
import 'package:dwds/src/web_socket_dwds_vm_client.dart';
4039
import 'package:logging/logging.dart';
4140
import 'package:shelf/shelf.dart';
4241
import 'package:sse/server/sse_handler.dart';

dwds/lib/src/services/app_debug_services.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import 'package:dwds/src/services/chrome_proxy_service.dart'
88
show ChromeProxyService;
99
import 'package:dwds/src/services/debug_service.dart';
1010
import 'package:dwds/src/services/proxy_service.dart';
11-
import 'package:dwds/src/web_socket_dwds_vm_client.dart';
1211

1312
/// Common interface for debug service containers.
1413
abstract class IAppDebugServices {
1514
IDebugService get debugService;
16-
dynamic get dwdsVmClient;
15+
IDwdsVmClient get dwdsVmClient;
1716
dynamic get dwdsStats;
1817
Uri? get ddsUri;
1918
String? get connectedInstanceId;
@@ -42,7 +41,7 @@ class AppDebugServices implements IAppDebugServices {
4241
DebugService get debugService => _debugService;
4342

4443
@override
45-
DwdsVmClient get dwdsVmClient => _dwdsVmClient;
44+
IDwdsVmClient get dwdsVmClient => _dwdsVmClient;
4645

4746
@override
4847
DwdsStats get dwdsStats => _dwdsStats;
@@ -78,7 +77,7 @@ class WebSocketAppDebugServices implements IAppDebugServices {
7877
WebSocketDebugService get debugService => _debugService;
7978

8079
@override
81-
WebSocketDwdsVmClient get dwdsVmClient => _dwdsVmClient;
80+
IDwdsVmClient get dwdsVmClient => _dwdsVmClient;
8281

8382
@override
8483
String? get connectedInstanceId => _connectedInstanceId;

0 commit comments

Comments
 (0)