@@ -50,6 +50,24 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
50
50
@override
51
51
bool get supportsRestartRequest => true ;
52
52
53
+ /// A list of reverse-requests from `flutter run --machine` that should be forwarded to the client.
54
+ final Set <String > _requestsToForwardToClient = < String > {
55
+ // The 'app.exposeUrl' request is sent by Flutter to request the client
56
+ // exposes a URL to the user and return the public version of that URL.
57
+ //
58
+ // This supports some web scenarios where the `flutter` tool may be running
59
+ // on a different machine to the user (for example a cloud IDE or in VS Code
60
+ // remote workspace) so we cannot just use the raw URL because the hostname
61
+ // and/or port might not be available to the machine the user is using.
62
+ // Instead, the IDE/infrastructure can set up port forwarding/proxying and
63
+ // return a user-facing URL that will map to the original (localhost) URL
64
+ // Flutter provided.
65
+ 'app.exposeUrl' ,
66
+ };
67
+
68
+ /// Completers for reverse requests from Flutter that may need to be handled by the client.
69
+ final Map <Object , Completer <Object ?>> _reverseRequestCompleters = < Object , Completer <Object ?>> {};
70
+
53
71
/// Whether or not the user requested debugging be enabled.
54
72
///
55
73
/// For debugging to be enabled, the user must have chosen "Debug" (and not
@@ -151,6 +169,13 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
151
169
sendResponse (null );
152
170
break ;
153
171
172
+ // Handle requests (from the client) that provide responses to reverse-requests
173
+ // that we forwarded from `flutter run --machine`.
174
+ case 'flutter.sendForwardedRequestResponse' :
175
+ _handleForwardedResponse (args);
176
+ sendResponse (null );
177
+ break ;
178
+
154
179
default :
155
180
await super .customRequest (request, args, sendResponse);
156
181
}
@@ -275,42 +300,41 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
275
300
sendResponse ();
276
301
}
277
302
278
- /// Sends a request to the Flutter daemon that is running/attaching to the app and waits for a response.
303
+ /// Sends a request to the Flutter run daemon that is running/attaching to the app and waits for a response.
279
304
///
280
- /// If [failSilently] is `true` (the default) and there is no process, the
281
- /// message will be silently ignored (this is common during the application
282
- /// being stopped, where async messages may be processed). Setting it to
283
- /// `false` will cause a [DebugAdapterException] to be thrown in that case.
305
+ /// If there is no process, the message will be silently ignored (this is
306
+ /// common during the application being stopped, where async messages may be
307
+ /// processed).
284
308
Future <Object ?> sendFlutterRequest (
285
309
String method,
286
- Map <String , Object ?>? params, {
287
- bool failSilently = true ,
288
- }) async {
289
- final Process ? process = this .process;
290
-
291
- if (process == null ) {
292
- if (failSilently) {
293
- return null ;
294
- } else {
295
- throw DebugAdapterException (
296
- 'Unable to Restart because Flutter process is not available' ,
297
- );
298
- }
299
- }
300
-
310
+ Map <String , Object ?>? params,
311
+ ) async {
301
312
final Completer <Object ?> completer = Completer <Object ?>();
302
313
final int id = _flutterRequestId++ ;
303
314
_flutterRequestCompleters[id] = completer;
304
315
316
+ sendFlutterMessage (< String , Object ? > {
317
+ 'id' : id,
318
+ 'method' : method,
319
+ 'params' : params,
320
+ });
321
+
322
+ return completer.future;
323
+ }
324
+
325
+ /// Sends a message to the Flutter run daemon.
326
+ ///
327
+ /// Throws `DebugAdapterException` if a Flutter process is not yet running.
328
+ void sendFlutterMessage (Map <String , Object ?> message) {
329
+ final Process ? process = this .process;
330
+ if (process == null ) {
331
+ throw DebugAdapterException ('Flutter process has not yet started' );
332
+ }
333
+
334
+ final String messageString = jsonEncode (message);
305
335
// Flutter requests are always wrapped in brackets as an array.
306
- final String messageString = jsonEncode (
307
- < String , Object ? > {'id' : id, 'method' : method, 'params' : params},
308
- );
309
336
final String payload = '[$messageString ]\n ' ;
310
-
311
337
process.stdin.writeln (payload);
312
-
313
- return completer.future;
314
338
}
315
339
316
340
/// Called by [terminateRequest] to request that we gracefully shut down the app being run (or in the case of an attach, disconnect).
@@ -432,6 +456,62 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
432
456
}
433
457
}
434
458
459
+ /// Handles incoming reverse requests from `flutter run --machine` .
460
+ ///
461
+ /// These requests are usually just forwarded to the client via an event
462
+ /// (`flutter.forwardedRequest` ) and responses are provided by the client in a
463
+ /// custom event (`flutter.forwardedRequestResponse` ).
464
+ void _handleJsonRequest (
465
+ Object id,
466
+ String method,
467
+ Map <String , Object ?>? params,
468
+ ) {
469
+ /// A helper to send a client response to Flutter.
470
+ void sendResponseToFlutter (Object ? id, Object ? value, { bool error = false }) {
471
+ sendFlutterMessage (< String , Object ? > {
472
+ 'id' : id,
473
+ if (error)
474
+ 'error' : value
475
+ else
476
+ 'result' : value
477
+ });
478
+ }
479
+
480
+ // Set up a completer to forward the response back to `flutter` when it arrives.
481
+ final Completer <Object ?> completer = Completer <Object ?>();
482
+ _reverseRequestCompleters[id] = completer;
483
+ completer.future
484
+ .then ((Object ? value) => sendResponseToFlutter (id, value))
485
+ .catchError ((Object ? e) => sendResponseToFlutter (id, e.toString (), error: true ));
486
+
487
+ if (_requestsToForwardToClient.contains (method)) {
488
+ // Forward the request to the client in an event.
489
+ sendEvent (
490
+ RawEventBody (< String , Object ? > {
491
+ 'id' : id,
492
+ 'method' : method,
493
+ 'params' : params,
494
+ }),
495
+ eventType: 'flutter.forwardedRequest' ,
496
+ );
497
+ } else {
498
+ completer.completeError (ArgumentError .value (method, 'Unknown request method.' ));
499
+ }
500
+ }
501
+
502
+ /// Handles client responses to reverse-requests that were forwarded from Flutter.
503
+ void _handleForwardedResponse (RawRequestArguments ? args) {
504
+ final Object ? id = args? .args['id' ];
505
+ final Object ? result = args? .args['result' ];
506
+ final Object ? error = args? .args['error' ];
507
+ final Completer <Object ?>? completer = _reverseRequestCompleters[id];
508
+ if (error != null ) {
509
+ completer? .completeError (DebugAdapterException ('Client reported an error handling reverse-request $error ' ));
510
+ } else {
511
+ completer? .complete (result);
512
+ }
513
+ }
514
+
435
515
/// Handles incoming JSON messages from `flutter run --machine` that are responses to requests that we sent.
436
516
void _handleJsonResponse (int id, Map <String , Object ?> response) {
437
517
final Completer <Object ?>? handler = _flutterRequestCompleters.remove (id);
@@ -509,10 +589,13 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
509
589
}
510
590
511
591
final Object ? event = payload['event' ];
592
+ final Object ? method = payload['method' ];
512
593
final Object ? params = payload['params' ];
513
594
final Object ? id = payload['id' ];
514
595
if (event is String && params is Map <String , Object ?>? ) {
515
596
_handleJsonEvent (event, params);
597
+ } else if (id != null && method is String && params is Map <String , Object ?>? ) {
598
+ _handleJsonRequest (id, method, params);
516
599
} else if (id is int && _flutterRequestCompleters.containsKey (id)) {
517
600
_handleJsonResponse (id, payload);
518
601
} else {
0 commit comments