@@ -25,57 +25,101 @@ import 'package:shelf/shelf.dart' as shelf;
25
25
import 'package:shelf/shelf.dart' hide Response;
26
26
import 'package:shelf_web_socket/shelf_web_socket.dart' ;
27
27
import 'package:sse/server/sse_handler.dart' ;
28
+ import 'package:stream_channel/stream_channel.dart' ;
29
+ import 'package:vm_service/vm_service.dart' ;
28
30
import 'package:vm_service_interface/vm_service_interface.dart' ;
29
31
import 'package:web_socket_channel/web_socket_channel.dart' ;
30
32
31
33
const _kSseHandlerPath = '\$ debugHandler' ;
32
34
33
35
bool _acceptNewConnections = true ;
34
- int _clientsConnected = 0 ;
36
+
37
+ final _clientConnections = < int , StreamChannel > {};
38
+ int _clientId = 0 ;
35
39
36
40
Logger _logger = Logger ('DebugService' );
37
41
42
+ void _handleConnection (
43
+ StreamChannel channel,
44
+ ChromeProxyService chromeProxyService,
45
+ ServiceExtensionRegistry serviceExtensionRegistry, {
46
+ void Function (Map <String , Object >)? onRequest,
47
+ void Function (Map <String , Object ?>)? onResponse,
48
+ }) {
49
+ final clientId = _clientId++ ;
50
+ final responseController = StreamController <Map <String , Object ?>>();
51
+ responseController.stream
52
+ .asyncMap ((response) async {
53
+ // This error indicates a successful invocation to _yieldControlToDDS.
54
+ // We don't have a good way to access the list of connected clients
55
+ // while also being able to determine which client invoked the RPC
56
+ // without some form of client ID.
57
+ //
58
+ // We can probably do better than this, but it will likely involve some
59
+ // refactoring.
60
+ if (response case {
61
+ 'error' : {
62
+ 'code' : DisconnectNonDartDevelopmentServiceClients .kErrorCode,
63
+ },
64
+ }) {
65
+ final nonDdsClients = _clientConnections.entries
66
+ .where ((MapEntry <int , StreamChannel > e) => e.key != clientId)
67
+ .map ((e) => e.value);
68
+ await Future .wait ([
69
+ for (final client in nonDdsClients) client.sink.close (),
70
+ ]);
71
+ // Remove the artificial error and return Success.
72
+ response.remove ('error' );
73
+ response['result' ] = Success ().toJson ();
74
+ }
75
+ if (onResponse != null ) onResponse (response);
76
+ channel.sink.add (jsonEncode (response));
77
+ })
78
+ .listen (channel.sink.add, onError: channel.sink.addError);
79
+ final inputStream = channel.stream.map ((value) {
80
+ if (value is List <int >) {
81
+ value = utf8.decode (value);
82
+ } else if (value is ! String ) {
83
+ throw StateError (
84
+ 'Got value with unexpected type ${value .runtimeType } from web '
85
+ 'socket, expected a List<int> or String.' ,
86
+ );
87
+ }
88
+ final request = Map <String , Object >.from (jsonDecode (value));
89
+ if (onRequest != null ) onRequest (request);
90
+ return request;
91
+ });
92
+ VmServerConnection (
93
+ inputStream,
94
+ responseController.sink,
95
+ serviceExtensionRegistry,
96
+ chromeProxyService,
97
+ ).done.whenComplete (() {
98
+ _clientConnections.remove (clientId);
99
+ if (! _acceptNewConnections && _clientConnections.isEmpty) {
100
+ // DDS has disconnected so we can allow for clients to connect directly
101
+ // to DWDS.
102
+ ChromeDebugService ._ddsUri = null ;
103
+ _acceptNewConnections = true ;
104
+ }
105
+ });
106
+ _clientConnections[clientId] = channel;
107
+ }
108
+
38
109
void Function (WebSocketChannel , String ? ) _createNewConnectionHandler (
39
110
ChromeProxyService chromeProxyService,
40
111
ServiceExtensionRegistry serviceExtensionRegistry, {
41
112
void Function (Map <String , Object >)? onRequest,
42
113
void Function (Map <String , Object ?>)? onResponse,
43
114
}) {
44
115
return (webSocket, subprotocol) {
45
- final responseController = StreamController <Map <String , Object ?>>();
46
- webSocket.sink.addStream (
47
- responseController.stream.map ((response) {
48
- if (onResponse != null ) onResponse (response);
49
- return jsonEncode (response);
50
- }),
51
- );
52
- final inputStream = webSocket.stream.map ((value) {
53
- if (value is List <int >) {
54
- value = utf8.decode (value);
55
- } else if (value is ! String ) {
56
- throw StateError (
57
- 'Got value with unexpected type ${value .runtimeType } from web '
58
- 'socket, expected a List<int> or String.' ,
59
- );
60
- }
61
- final request = Map <String , Object >.from (jsonDecode (value));
62
- if (onRequest != null ) onRequest (request);
63
- return request;
64
- });
65
- ++ _clientsConnected;
66
- VmServerConnection (
67
- inputStream,
68
- responseController.sink,
69
- serviceExtensionRegistry,
116
+ _handleConnection (
117
+ webSocket,
70
118
chromeProxyService,
71
- ).done.whenComplete (() {
72
- -- _clientsConnected;
73
- if (! _acceptNewConnections && _clientsConnected == 0 ) {
74
- // DDS has disconnected so we can allow for clients to connect directly
75
- // to DWDS.
76
- _acceptNewConnections = true ;
77
- }
78
- });
119
+ serviceExtensionRegistry,
120
+ onRequest: onRequest,
121
+ onResponse: onResponse,
122
+ );
79
123
};
80
124
}
81
125
@@ -88,41 +132,12 @@ Future<void> _handleSseConnections(
88
132
}) async {
89
133
while (await handler.connections.hasNext) {
90
134
final connection = await handler.connections.next;
91
- final responseController = StreamController <Map <String , Object ?>>();
92
- final sub = responseController.stream
93
- .map ((response) {
94
- if (onResponse != null ) onResponse (response);
95
- return jsonEncode (response);
96
- })
97
- .listen (connection.sink.add);
98
- safeUnawaited (
99
- chromeProxyService.remoteDebugger.onClose.first.whenComplete (() {
100
- connection.sink.close ();
101
- sub.cancel ();
102
- }),
103
- );
104
- final inputStream = connection.stream.map ((value) {
105
- final request = jsonDecode (value) as Map <String , Object >;
106
- if (onRequest != null ) onRequest (request);
107
- return request;
108
- });
109
- ++ _clientsConnected;
110
- final vmServerConnection = VmServerConnection (
111
- inputStream,
112
- responseController.sink,
113
- serviceExtensionRegistry,
135
+ _handleConnection (
136
+ connection,
114
137
chromeProxyService,
115
- );
116
- safeUnawaited (
117
- vmServerConnection.done.whenComplete (() {
118
- -- _clientsConnected;
119
- if (! _acceptNewConnections && _clientsConnected == 0 ) {
120
- // DDS has disconnected so we can allow for clients to connect directly
121
- // to DWDS.
122
- _acceptNewConnections = true ;
123
- }
124
- return sub.cancel ();
125
- }),
138
+ serviceExtensionRegistry,
139
+ onRequest: onRequest,
140
+ onResponse: onResponse,
126
141
);
127
142
}
128
143
}
@@ -224,15 +239,19 @@ class ChromeDebugService implements DebugService {
224
239
return _encodedUri = encoded;
225
240
}
226
241
227
- // TODO(https://github.com/dart-lang/webdev/issues/2399): yieldControlToDDS
228
- // should disconnect existing non-DDS clients.
229
- static bool yieldControlToDDS (String uri) {
230
- if (_clientsConnected > 1 ) {
231
- return false ;
242
+ static void yieldControlToDDS (String uri) {
243
+ if (_ddsUri != null ) {
244
+ // This exception is identical to the one thrown from
245
+ // sdk/lib/vmservice/vmservice.dart
246
+ throw RPCError (
247
+ '_yieldControlToDDS' ,
248
+ RPCErrorKind .kFeatureDisabled.code,
249
+ 'A DDS instance is already connected at $_ddsUri .' ,
250
+ {'ddsUri' : _ddsUri.toString ()},
251
+ );
232
252
}
233
- _ddsUri = uri;
234
253
_acceptNewConnections = false ;
235
- return true ;
254
+ _ddsUri = uri ;
236
255
}
237
256
238
257
static Future <ChromeDebugService > start (
@@ -441,6 +460,7 @@ class WebSocketDebugService implements DebugService {
441
460
WebSocketProxyService webSocketProxyService,
442
461
) {
443
462
return webSocketHandler ((WebSocketChannel webSocket, String ? subprotocol) {
463
+ final clientId = _clientId++ ;
444
464
final responseController = StreamController <Map <String , Object ?>>();
445
465
webSocket.sink.addStream (responseController.stream.map (jsonEncode));
446
466
@@ -455,14 +475,15 @@ class WebSocketDebugService implements DebugService {
455
475
return Map <String , Object >.from (jsonDecode (value));
456
476
});
457
477
458
- ++ _clientsConnected;
478
+ _clientConnections[clientId] = webSocket;
479
+
459
480
VmServerConnection (
460
481
inputStream,
461
482
responseController.sink,
462
483
serviceExtensionRegistry,
463
484
webSocketProxyService,
464
485
).done.whenComplete (() {
465
- -- _clientsConnected ;
486
+ _clientConnections. remove (clientId) ;
466
487
});
467
488
});
468
489
}
0 commit comments