@@ -50,6 +50,24 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
5050 @override
5151 bool get supportsRestartRequest => true ;
5252
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+
5371 /// Whether or not the user requested debugging be enabled.
5472 ///
5573 /// For debugging to be enabled, the user must have chosen "Debug" (and not
@@ -151,6 +169,13 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
151169 sendResponse (null );
152170 break ;
153171
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+
154179 default :
155180 await super .customRequest (request, args, sendResponse);
156181 }
@@ -275,42 +300,41 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
275300 sendResponse ();
276301 }
277302
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.
279304 ///
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).
284308 Future <Object ?> sendFlutterRequest (
285309 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 {
301312 final Completer <Object ?> completer = Completer <Object ?>();
302313 final int id = _flutterRequestId++ ;
303314 _flutterRequestCompleters[id] = completer;
304315
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);
305335 // 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- );
309336 final String payload = '[$messageString ]\n ' ;
310-
311337 process.stdin.writeln (payload);
312-
313- return completer.future;
314338 }
315339
316340 /// 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 {
432456 }
433457 }
434458
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+
435515 /// Handles incoming JSON messages from `flutter run --machine` that are responses to requests that we sent.
436516 void _handleJsonResponse (int id, Map <String , Object ?> response) {
437517 final Completer <Object ?>? handler = _flutterRequestCompleters.remove (id);
@@ -509,10 +589,13 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
509589 }
510590
511591 final Object ? event = payload['event' ];
592+ final Object ? method = payload['method' ];
512593 final Object ? params = payload['params' ];
513594 final Object ? id = payload['id' ];
514595 if (event is String && params is Map <String , Object ?>? ) {
515596 _handleJsonEvent (event, params);
597+ } else if (id != null && method is String && params is Map <String , Object ?>? ) {
598+ _handleJsonRequest (id, method, params);
516599 } else if (id is int && _flutterRequestCompleters.containsKey (id)) {
517600 _handleJsonResponse (id, payload);
518601 } else {
0 commit comments