@@ -9,6 +9,7 @@ import 'package:dwds/src/events.dart';
9
9
import 'package:dwds/src/services/chrome_debug_exception.dart' ;
10
10
import 'package:dwds/src/services/chrome_proxy_service.dart' ;
11
11
import 'package:dwds/src/services/debug_service.dart' ;
12
+ import 'package:dwds/src/services/web_socket_proxy_service.dart' ;
12
13
import 'package:dwds/src/utilities/synchronized.dart' ;
13
14
import 'package:logging/logging.dart' ;
14
15
import 'package:uuid/uuid.dart' ;
@@ -17,8 +18,6 @@ import 'package:vm_service/vm_service_io.dart';
17
18
import 'package:vm_service_interface/vm_service_interface.dart' ;
18
19
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' ;
19
20
20
- final _logger = Logger ('DwdsVmClient' );
21
-
22
21
/// Type of requests added to the request controller.
23
22
typedef VmRequest = Map <String , Object >;
24
23
@@ -38,9 +37,22 @@ enum _NamespacedServiceExtension {
38
37
final String method;
39
38
}
40
39
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
+
41
52
// A client of the vm service that registers some custom extensions like
42
53
// hotRestart.
43
- class DwdsVmClient {
54
+ class DwdsVmClient implements IDwdsVmClient {
55
+ @override
44
56
final VmService client;
45
57
final StreamController <Map <String , Object >> _requestController;
46
58
final StreamController <Map <String , Object ?>> _responseController;
@@ -137,7 +149,7 @@ class DwdsVmClient {
137
149
}) {
138
150
final client = VmService (responseStream.map (jsonEncode), (request) {
139
151
if (requestController.isClosed) {
140
- _logger .warning (
152
+ _chromeLogger .warning (
141
153
'Attempted to send a request but the connection is closed:\n\n '
142
154
'$request ' ,
143
155
);
@@ -331,13 +343,13 @@ void _processSendEvent(Map<String, dynamic> request, DwdsStats dwdsStats) {
331
343
switch (type) {
332
344
case 'DevtoolsEvent' :
333
345
{
334
- _logger .finest ('Received DevTools event: $event ' );
346
+ _chromeLogger .finest ('Received DevTools event: $event ' );
335
347
final action = payload? ['action' ] as String ? ;
336
348
final screen = payload? ['screen' ] as String ? ;
337
349
if (screen != null && action == 'pageReady' ) {
338
350
_recordDwdsStats (dwdsStats, screen);
339
351
} else {
340
- _logger .finest ('Ignoring unknown event: $event ' );
352
+ _chromeLogger .finest ('Ignoring unknown event: $event ' );
341
353
}
342
354
}
343
355
}
@@ -351,16 +363,16 @@ void _recordDwdsStats(DwdsStats dwdsStats, String screen) {
351
363
final devToolLoadTime =
352
364
DateTime .now ().difference (devToolsStart).inMilliseconds;
353
365
emitEvent (DwdsEvent .devToolsLoad (devToolLoadTime, screen));
354
- _logger .fine ('DevTools load time: $devToolLoadTime ms' );
366
+ _chromeLogger .fine ('DevTools load time: $devToolLoadTime ms' );
355
367
}
356
368
if (debuggerStart != null ) {
357
369
final debuggerReadyTime =
358
370
DateTime .now ().difference (debuggerStart).inMilliseconds;
359
371
emitEvent (DwdsEvent .debuggerReady (debuggerReadyTime, screen));
360
- _logger .fine ('Debugger ready time: $debuggerReadyTime ms' );
372
+ _chromeLogger .fine ('Debugger ready time: $debuggerReadyTime ms' );
361
373
}
362
374
} else {
363
- _logger .finest ('Debugger and DevTools stats are already recorded.' );
375
+ _chromeLogger .finest ('Debugger and DevTools stats are already recorded.' );
364
376
}
365
377
}
366
378
@@ -381,14 +393,14 @@ Future<Map<String, dynamic>> _hotRestart(
381
393
ChromeProxyService chromeProxyService,
382
394
VmService client,
383
395
) async {
384
- _logger .info ('Attempting a hot restart' );
396
+ _chromeLogger .info ('Attempting a hot restart' );
385
397
386
398
chromeProxyService.terminatingIsolates = true ;
387
399
await _disableBreakpointsAndResume (client, chromeProxyService);
388
400
try {
389
- _logger .info ('Attempting to get execution context ID.' );
401
+ _chromeLogger .info ('Attempting to get execution context ID.' );
390
402
await tryGetContextId (chromeProxyService);
391
- _logger .info ('Got execution context ID.' );
403
+ _chromeLogger .info ('Got execution context ID.' );
392
404
} on StateError catch (e) {
393
405
// We couldn't find the execution context. `hotRestart` may have been
394
406
// triggered in the middle of a full reload.
@@ -408,12 +420,12 @@ Future<Map<String, dynamic>> _hotRestart(
408
420
}
409
421
// Generate run id to hot restart all apps loaded into the tab.
410
422
final runId = const Uuid ().v4 ().toString ();
411
- _logger .info ('Issuing \$ dartHotRestartDwds request' );
423
+ _chromeLogger .info ('Issuing \$ dartHotRestartDwds request' );
412
424
await chromeProxyService.inspector.jsEvaluate (
413
425
'\$ dartHotRestartDwds(\' $runId \' , $pauseIsolatesOnStart );' ,
414
426
awaitPromise: true ,
415
427
);
416
- _logger .info ('\$ dartHotRestartDwds request complete.' );
428
+ _chromeLogger .info ('\$ dartHotRestartDwds request complete.' );
417
429
} on WipError catch (exception) {
418
430
final code = exception.error? ['code' ];
419
431
final message = exception.error? ['message' ];
@@ -433,11 +445,11 @@ Future<Map<String, dynamic>> _hotRestart(
433
445
},
434
446
};
435
447
}
436
- _logger .info ('Waiting for Isolate Start event.' );
448
+ _chromeLogger .info ('Waiting for Isolate Start event.' );
437
449
await stream.firstWhere ((event) => event.kind == EventKind .kIsolateStart);
438
450
chromeProxyService.terminatingIsolates = false ;
439
451
440
- _logger .info ('Successful hot restart' );
452
+ _chromeLogger .info ('Successful hot restart' );
441
453
return {'result' : Success ().toJson ()};
442
454
}
443
455
@@ -455,18 +467,20 @@ void _waitForResumeEventToRunMain(ChromeProxyService chromeProxyService) {
455
467
Future <Map <String , dynamic >> _fullReload (
456
468
ChromeProxyService chromeProxyService,
457
469
) async {
458
- _logger .info ('Attempting a full reload' );
470
+ _chromeLogger .info ('Attempting a full reload' );
459
471
await chromeProxyService.remoteDebugger.enablePage ();
460
472
await chromeProxyService.remoteDebugger.pageReload ();
461
- _logger .info ('Successful full reload' );
473
+ _chromeLogger .info ('Successful full reload' );
462
474
return {'result' : Success ().toJson ()};
463
475
}
464
476
465
477
Future <void > _disableBreakpointsAndResume (
466
478
VmService client,
467
479
ChromeProxyService chromeProxyService,
468
480
) 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
+ );
470
484
final vm = await client.getVM ();
471
485
final isolates = vm.isolates;
472
486
if (isolates == null || isolates.isEmpty) {
@@ -495,9 +509,193 @@ Future<void> _disableBreakpointsAndResume(
495
509
await client.resume (isolateId);
496
510
} on RPCError catch (e, s) {
497
511
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
+ );
499
517
rethrow ;
500
518
}
501
519
}
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
+ }
503
701
}
0 commit comments