Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
FlutterDartVMServicePublisher.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #define FML_USED_ON_EMBEDDER
6 
8 
9 #if FLUTTER_RELEASE
10 
11 @implementation FlutterDartVMServicePublisher
12 - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublication {
13  return [super init];
14 }
15 @end
16 
17 #else // FLUTTER_RELEASE
18 
19 #import <TargetConditionals.h>
20 // NSNetService works fine on physical devices before iOS 13.2.
21 // However, it doesn't expose the services to regular mDNS
22 // queries on the Simulator or on iOS 13.2+ devices.
23 //
24 // When debugging issues with this implementation, the following is helpful:
25 //
26 // 1) Running `dns-sd -Z _dartVmService`. This is a built-in macOS tool that
27 // can find advertized observatories using this method. If dns-sd can't find
28 // it, then the VM service is not getting advertised over any network
29 // interface that the host machine has access to.
30 // 2) The Python zeroconf package. The dns-sd tool can sometimes see things
31 // that aren't advertizing over a network interface - for example, simulators
32 // using NSNetService has been observed using dns-sd, but doesn't show up in
33 // the Python package (which is a high quality socket based implementation).
34 // If that happens, this code should be tweaked such that it shows up in both
35 // dns-sd's output and Python zeroconf's detection.
36 // 3) The Dart multicast_dns package, which is what Flutter uses to find the
37 // port and auth code. If the advertizement shows up in dns-sd and Python
38 // zeroconf but not multicast_dns, then it is a bug in multicast_dns.
39 #include <dns_sd.h>
40 #include <net/if.h>
41 
42 #include "flutter/fml/logging.h"
43 #include "flutter/fml/message_loop.h"
44 #include "flutter/runtime/dart_service_isolate.h"
46 
48 
50 - (void)publishServiceProtocolPort:(NSURL*)uri;
51 - (void)stopService;
52 @end
53 
55 + (NSData*)createTxtData:(NSURL*)url;
56 
57 @property(readonly, class) NSString* serviceName;
58 @property(readonly) NSObject<FlutterDartVMServicePublisherDelegate>* delegate;
59 @property(nonatomic, readwrite) NSURL* url;
60 @property(readonly) BOOL enableVMServicePublication;
61 
62 @end
63 
65 @end
66 
67 @implementation DartVMServiceDNSServiceDelegate {
68  DNSServiceRef _dnsServiceRef;
69 }
70 
71 - (void)stopService {
72  if (_dnsServiceRef) {
73  DNSServiceRefDeallocate(_dnsServiceRef);
74  _dnsServiceRef = NULL;
75  }
76 }
77 
78 - (void)publishServiceProtocolPort:(NSURL*)url {
79  // TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836
80  FML_LOG(INFO) << "Publish Service Protocol Port";
81  DNSServiceFlags flags = kDNSServiceFlagsDefault;
82 #if TARGET_IPHONE_SIMULATOR
83  // Simulator needs to use local loopback explicitly to work.
84  uint32_t interfaceIndex = if_nametoindex("lo0");
85 #else // TARGET_IPHONE_SIMULATOR
86  // Physical devices need to request all interfaces.
87  uint32_t interfaceIndex = 0;
88 #endif // TARGET_IPHONE_SIMULATOR
89  const char* registrationType = "_dartVmService._tcp";
90 
91  const char* domain = "local."; // default domain
92  uint16_t port = [[url port] unsignedShortValue];
93 
94  NSData* txtData = [FlutterDartVMServicePublisher createTxtData:url];
95  int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex,
96  FlutterDartVMServicePublisher.serviceName.UTF8String,
97  registrationType, domain, NULL, htons(port), txtData.length,
98  txtData.bytes, RegistrationCallback, NULL);
99 
100  if (err == 0) {
101  DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue());
102  return;
103  }
104 
105  FML_LOG(ERROR) << "Failed to register Dart VM Service port with mDNS with error " << err << ".";
106  if (@available(iOS 14.0, *)) {
107  FML_LOG(ERROR) << "On iOS 14+, local network broadcast in apps need to be declared in "
108  << "the app's Info.plist. Debug and profile Flutter apps and modules host "
109  << "VM services on the local network to support debugging features such "
110  << "as hot reload and DevTools. To make your Flutter app or module "
111  << "attachable and debuggable, add a '" << registrationType << "' value "
112  << "to the 'NSBonjourServices' key in your Info.plist for the Debug/"
113  << "Profile configurations. " << "For more information, see "
114  << "https://flutter.dev/docs/development/add-to-app/ios/"
115  "project-setup#local-network-privacy-permissions";
116  }
117 }
118 
119 static void DNSSD_API RegistrationCallback(DNSServiceRef sdRef,
120  DNSServiceFlags flags,
121  DNSServiceErrorType errorCode,
122  const char* name,
123  const char* regType,
124  const char* domain,
125  void* context) {
126  if (errorCode == kDNSServiceErr_NoError) {
127  FML_DLOG(INFO) << "FlutterDartVMServicePublisher is ready!";
128  } else if (errorCode == kDNSServiceErr_PolicyDenied) {
129  FML_LOG(ERROR)
130  << "Could not register as server for FlutterDartVMServicePublisher, permission "
131  << "denied. Check your 'Local Network' permissions for this app in the Privacy section of "
132  << "the system Settings.";
133  } else {
134  FML_LOG(ERROR) << "Could not register as server for FlutterDartVMServicePublisher. Check your "
135  "network settings and relaunch the application.";
136  }
137 }
138 
139 @end
140 
141 @implementation FlutterDartVMServicePublisher {
142  flutter::DartServiceIsolate::CallbackHandle _callbackHandle;
143 }
144 
145 - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublication {
146  self = [super init];
147  NSAssert(self, @"Super must not return null on init.");
148 
149  _delegate = [[DartVMServiceDNSServiceDelegate alloc] init];
150  _enableVMServicePublication = enableVMServicePublication;
151  __weak __typeof(self) weakSelf = self;
152 
153  fml::MessageLoop::EnsureInitializedForCurrentThread();
154 
155  _callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback(
156  [weakSelf, runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) {
157  if (!uri.empty()) {
158  runner->PostTask([weakSelf, uri]() {
159  FlutterDartVMServicePublisher* strongSelf = weakSelf;
160  // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
161  // number.
162  if (strongSelf) {
163  NSURL* url =
164  [[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]];
165  strongSelf.url = url;
166  if (strongSelf.enableVMServicePublication) {
167  [[strongSelf delegate] publishServiceProtocolPort:url];
168  }
169  }
170  });
171  }
172  });
173 
174  return self;
175 }
176 
177 + (NSString*)serviceName {
178  return NSBundle.mainBundle.bundleIdentifier;
179 }
180 
181 + (NSData*)createTxtData:(NSURL*)url {
182  // Check to see if there's an authentication code. If there is, we'll provide
183  // it as a txt record so flutter tools can establish a connection.
184  NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])];
185  NSData* pathData = [path dataUsingEncoding:NSUTF8StringEncoding];
186  NSDictionary<NSString*, NSData*>* txtDict = @{
187  @"authCode" : pathData,
188  };
189  return [NSNetService dataFromTXTRecordDictionary:txtDict];
190 }
191 
192 - (void)dealloc {
193  [_delegate stopService];
194 
195  flutter::DartServiceIsolate::RemoveServerStatusCallback(_callbackHandle);
196 }
197 @end
198 
199 #endif // FLUTTER_RELEASE
FlutterDartVMServicePublisher::url
NSURL * url
Definition: FlutterDartVMServicePublisher.h:17
FlutterDartVMServicePublisher.h
FlutterMacros.h
FlutterDartVMServicePublisherDelegate-p
Definition: FlutterDartVMServicePublisher.mm:49
-[FlutterDartVMServicePublisherDelegate-p stopService]
void stopService()
DartVMServiceDNSServiceDelegate
Definition: FlutterDartVMServicePublisher.mm:64
FlutterDartVMServicePublisher
Definition: FlutterDartVMServicePublisher.h:10
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13