44
55import  'dart:async' ;
66
7- import  'package:multicast_dns/multicast_dns.dart' ;
8- 
97import  '../artifacts.dart' ;
108import  '../base/common.dart' ;
119import  '../base/context.dart' ;
@@ -21,6 +19,7 @@ import '../fuchsia/fuchsia_device.dart';
2119import  '../globals.dart' ;
2220import  '../ios/devices.dart' ;
2321import  '../ios/simulators.dart' ;
22+ import  '../mdns_discovery.dart' ;
2423import  '../project.dart' ;
2524import  '../protocol_discovery.dart' ;
2625import  '../resident_runner.dart' ;
@@ -207,7 +206,7 @@ class AttachCommand extends FlutterCommand {
207206    final  String  hostname =  usesIpv6 ?  ipv6Loopback :  ipv4Loopback;
208207
209208    bool  attachLogger =  false ;
210-     if  (devicePort ==  null    &&  debugUri ==  null ) {
209+     if  (devicePort ==  null  &&  debugUri ==  null ) {
211210      if  (device is  FuchsiaDevice ) {
212211        attachLogger =  true ;
213212        final  String  module =  argResults['module' ];
@@ -229,10 +228,11 @@ class AttachCommand extends FlutterCommand {
229228          rethrow ;
230229        }
231230      } else  if  ((device is  IOSDevice ) ||  (device is  IOSSimulator )) {
232-         final  MDnsObservatoryDiscoveryResult  result =  await  MDnsObservatoryDiscovery ().query (applicationId:  appId);
233-         if  (result !=  null ) {
234-           observatoryUri =  await  _buildObservatoryUri (device, hostname, result.port, result.authCode);
235-         }
231+         observatoryUri =  await  MDnsObservatoryDiscovery .instance.getObservatoryUri (
232+           appId,
233+           device,
234+           usesIpv6,
235+         );
236236      }
237237      // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery. 
238238      if  (observatoryUri ==  null ) {
@@ -254,8 +254,13 @@ class AttachCommand extends FlutterCommand {
254254        }
255255      }
256256    } else  {
257-       observatoryUri =  await  _buildObservatoryUri (device,
258-           debugUri? .host ??  hostname, devicePort ??  debugUri.port, debugUri? .path);
257+       observatoryUri =  await  buildObservatoryUri (
258+         device,
259+         debugUri? .host ??  hostname,
260+         devicePort ??  debugUri.port,
261+         observatoryPort,
262+         debugUri? .path,
263+       );
259264    }
260265    try  {
261266      final  bool  useHot =  getBuildInfo ().isDebug;
@@ -337,22 +342,6 @@ class AttachCommand extends FlutterCommand {
337342  }
338343
339344  Future <void > _validateArguments () async  { }
340- 
341-   Future <Uri > _buildObservatoryUri (Device  device,
342-       String  host, int  devicePort, [String  authCode]) async  {
343-     String  path =  '/' ;
344-     if  (authCode !=  null ) {
345-       path =  authCode;
346-     }
347-     // Not having a trailing slash can cause problems in some situations. 
348-     // Ensure that there's one present. 
349-     if  (! path.endsWith ('/' )) {
350-       path +=  '/' ;
351-     }
352-     final  int  localPort =  observatoryPort
353-         ??  await  device.portForwarder.forward (devicePort);
354-     return  Uri (scheme:  'http' , host:  host, port:  localPort, path:  path);
355-   }
356345}
357346
358347class  HotRunnerFactory  {
@@ -383,132 +372,3 @@ class HotRunnerFactory {
383372    ipv6:  ipv6,
384373  );
385374}
386- 
387- class  MDnsObservatoryDiscoveryResult  {
388-   MDnsObservatoryDiscoveryResult (this .port, this .authCode);
389-   final  int  port;
390-   final  String  authCode;
391- }
392- 
393- /// A wrapper around [MDnsClient]  to find a Dart observatory instance. 
394- class  MDnsObservatoryDiscovery  {
395-   /// Creates a new [MDnsObservatoryDiscovery]  object. 
396-   /// 
397-   /// The [client]  parameter will be defaulted to a new [MDnsClient]  if null. 
398-   /// The [applicationId]  parameter may be null, and can be used to 
399-   /// automatically select which application to use if multiple are advertising 
400-   /// Dart observatory ports. 
401-    MDnsObservatoryDiscovery ({MDnsClient  mdnsClient})
402-     :  client =  mdnsClient ??  MDnsClient ();
403- 
404-   /// The [MDnsClient]  used to do a lookup. 
405-    final  MDnsClient  client;
406- 
407-   static  const  String  dartObservatoryName =  '_dartobservatory._tcp.local' ;
408- 
409-   /// Executes an mDNS query for a Dart Observatory. 
410-   /// 
411-   /// The [applicationId]  parameter may be used to specify which application 
412-   /// to find.  For Android, it refers to the package name; on iOS, it refers to 
413-   /// the bundle ID. 
414-   /// 
415-   /// If it is not null, this method will find the port and authentication code 
416-   /// of the Dart Observatory for that application. If it cannot find a Dart 
417-   /// Observatory matching that application identifier, it will call 
418-   /// [throwToolExit] . 
419-   /// 
420-   /// If it is null and there are multiple ports available, the user will be 
421-   /// prompted with a list of available observatory ports and asked to select 
422-   /// one. 
423-   /// 
424-   /// If it is null and there is only one available instance of Observatory, 
425-   /// it will return that instance's information regardless of what application 
426-   /// the Observatory instance is for. 
427-    Future <MDnsObservatoryDiscoveryResult > query ({String  applicationId}) async  {
428-     printStatus ('Checking for advertised Dart observatories...' );
429-     try  {
430-       await  client.start ();
431-       final  List <PtrResourceRecord > pointerRecords =  await  client
432-           .lookup <PtrResourceRecord >(
433-             ResourceRecordQuery .serverPointer (dartObservatoryName),
434-           )
435-           .toList ();
436-       if  (pointerRecords.isEmpty) {
437-         return  null ;
438-       }
439-       // We have no guarantee that we won't get multiple hits from the same 
440-       // service on this. 
441-       final  List <String > uniqueDomainNames =  pointerRecords
442-           .map <String >((PtrResourceRecord  record) =>  record.domainName)
443-           .toSet ()
444-           .toList ();
445- 
446-       String  domainName;
447-       if  (applicationId !=  null ) {
448-         for  (String  name in  uniqueDomainNames) {
449-           if  (name.toLowerCase ().startsWith (applicationId.toLowerCase ())) {
450-             domainName =  name;
451-             break ;
452-           }
453-         }
454-         if  (domainName ==  null ) {
455-           throwToolExit ('Did not find a observatory port advertised for $applicationId .' );
456-         }
457-       } else  if  (uniqueDomainNames.length >  1 ) {
458-         final  StringBuffer  buffer =  StringBuffer ();
459-         buffer.writeln ('There are multiple observatory ports available.' );
460-         buffer.writeln ('Rerun this command with one of the following passed in as the appId:' );
461-         buffer.writeln ('' );
462-          for  (final  String  uniqueDomainName in  uniqueDomainNames) {
463-           buffer.writeln ('  flutter attach --app-id ${uniqueDomainName .replaceAll ('.$dartObservatoryName ' , '' )}' );
464-         }
465-         throwToolExit (buffer.toString ());
466-       } else  {
467-         domainName =  pointerRecords[0 ].domainName;
468-       }
469-       printStatus ('Checking for available port on $domainName ' );
470-       // Here, if we get more than one, it should just be a duplicate. 
471-       final  List <SrvResourceRecord > srv =  await  client
472-           .lookup <SrvResourceRecord >(
473-             ResourceRecordQuery .service (domainName),
474-           )
475-           .toList ();
476-       if  (srv.isEmpty) {
477-         return  null ;
478-       }
479-       if  (srv.length >  1 ) {
480-         printError ('Unexpectedly found more than one observatory report for $domainName  ' 
481-                    '- using first one (${srv .first .port }).' );
482-       }
483-       printStatus ('Checking for authentication code for $domainName ' );
484-       final  List <TxtResourceRecord > txt =  await  client
485-         .lookup <TxtResourceRecord >(
486-             ResourceRecordQuery .text (domainName),
487-         )
488-         ? .toList ();
489-       if  (txt ==  null  ||  txt.isEmpty) {
490-         return  MDnsObservatoryDiscoveryResult (srv.first.port, '' );
491-       }
492-       String  authCode =  '' ;
493-       const  String  authCodePrefix =  'authCode=' ;
494-       String  raw =  txt.first.text;
495-       // TXT has a format of [<length byte>, text], so if the length is 2, 
496-       // that means that TXT is empty. 
497-       if  (raw.length >  2 ) {
498-         // Remove length byte from raw txt. 
499-         raw =  raw.substring (1 );
500-         if  (raw.startsWith (authCodePrefix)) {
501-           authCode =  raw.substring (authCodePrefix.length);
502-           // The Observatory currently expects a trailing '/' as part of the 
503-           // URI, otherwise an invalid authentication code response is given. 
504-           if  (! authCode.endsWith ('/' )) {
505-             authCode +=  '/' ;
506-           }
507-         }
508-       }
509-       return  MDnsObservatoryDiscoveryResult (srv.first.port, authCode);
510-     } finally  {
511-       client.stop ();
512-     }
513-   }
514- }
0 commit comments