| Flutter iOS Embedder |

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
7 #import "FlutterDartVMServicePublisher.h"
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"
45 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
46 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
47
48 FLUTTER_ASSERT_ARC
49
50 @protocol FlutterDartVMServicePublisherDelegate
51 - (void)publishServiceProtocolPort:(NSURL*)uri;
52 - (void)stopService;
53 @end
54
55 @interface FlutterDartVMServicePublisher ()
56 + (NSData*)createTxtData:(NSURL*)url;
57
58 @property(readonly, class) NSString* serviceName;
59 @property(readonly) NSObject<FlutterDartVMServicePublisherDelegate>* delegate;
60 @property(nonatomic, readwrite) NSURL* url;
61 @property(readonly) BOOL enableVMServicePublication;
62
63 @end
64
65 @interface DartVMServiceDNSServiceDelegate : NSObject <FlutterDartVMServicePublisherDelegate>
66 @end
67
68 @implementation DartVMServiceDNSServiceDelegate {
69 DNSServiceRef _dnsServiceRef;
70 }
71
72 - (void)stopService {
73 if (_dnsServiceRef) {
74 DNSServiceRefDeallocate(_dnsServiceRef);
75 _dnsServiceRef = NULL;
76 }
77 }
78
79 - (void)publishServiceProtocolPort:(NSURL*)url {
80 DNSServiceFlags flags = kDNSServiceFlagsDefault;
81 #if TARGET_IPHONE_SIMULATOR
82 // Simulator needs to use local loopback explicitly to work.
83 uint32_t interfaceIndex = if_nametoindex("lo0");
84 #else // TARGET_IPHONE_SIMULATOR
85 // Physical devices need to request all interfaces.
86 uint32_t interfaceIndex = 0;
87 #endif // TARGET_IPHONE_SIMULATOR
88 const char* registrationType = "_dartVmService._tcp";
89
90 const char* domain = "local."; // default domain
91 uint16_t port = [[url port] unsignedShortValue];
92
93 NSData* txtData = [FlutterDartVMServicePublisher createTxtData:url];
94 int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex,
95 FlutterDartVMServicePublisher.serviceName.UTF8String,
96 registrationType, domain, NULL, htons(port), txtData.length,
97 txtData.bytes, RegistrationCallback, NULL);
98
99 if (err == 0) {
100 DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue());
101 return;
102 }
103
104 NSString* errorMessage = [NSString
105 stringWithFormat:@"Failed to register Dart VM Service port with mDNS with error %d.", err];
106 [FlutterLogger logError:errorMessage];
107 if (@available(iOS 14.0, *)) {
108 errorMessage = [NSString
109 stringWithFormat:@"On iOS 14+, local network broadcast in apps need to be declared in "
110 "the app's Info.plist. Debug and profile Flutter apps and modules host "
111 "VM services on the local network to support debugging features such "
112 "as hot reload and DevTools. To make your Flutter app or module "
113 "attachable and debuggable, add a '%s' value to the 'NSBonjourServices' "
114 "key in your Info.plist for the Debug/Profile configurations. For more "
115 "information, see https://docs.flutter.dev/development/add-to-app/ios/"
116 "project-setup#local-network-privacy-permissions",
117 registrationType];
118 [FlutterLogger logError:errorMessage];
119 }
120 }
121
122 static void DNSSD_API RegistrationCallback(DNSServiceRef sdRef,
123 DNSServiceFlags flags,
124 DNSServiceErrorType errorCode,
125 const char* name,
126 const char* regType,
127 const char* domain,
128 void* context) {
129 if (errorCode == kDNSServiceErr_NoError) {
130 FML_DLOG(INFO) << "FlutterDartVMServicePublisher is ready!";
131 } else if (errorCode == kDNSServiceErr_PolicyDenied) {
132 // Local Network permissions on simulators stopped working in macOS 15.4 and will always return
133 // kDNSServiceErr_PolicyDenied. See
134 // https://github.com/flutter/flutter/issues/166333#issuecomment-2786720560.
135 #if TARGET_IPHONE_SIMULATOR
136 FML_DLOG(WARNING)
137 << "Could not register as server for FlutterDartVMServicePublisher, permission "
138 << "denied. Check your 'Local Network' permissions for this app in the Privacy section of "
139 << "the system Settings.";
140 #else // TARGET_IPHONE_SIMULATOR
141 [FlutterLogger logError:@"Could not register as server for FlutterDartVMServicePublisher, "
142 "permission denied. Check your 'Local Network' permissions for this "
143 "app in the Privacy section of the system Settings."];
144 #endif // TARGET_IPHONE_SIMULATOR
145 } else {
146 [FlutterLogger logError:@"Could not register as server for FlutterDartVMServicePublisher. "
147 "Check your network settings and relaunch the application."];
148 }
149 }
150
151 @end
152
153 @implementation FlutterDartVMServicePublisher {
154 flutter::DartServiceIsolate::CallbackHandle _callbackHandle;
155 }
156
157 - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublication {
158 self = [super init];
159 NSAssert(self, @"Super must not return null on init.");
160
161 _delegate = [[DartVMServiceDNSServiceDelegate alloc] init];
162 _enableVMServicePublication = enableVMServicePublication;
163 __weak __typeof(self) weakSelf = self;
164
165 fml::MessageLoop::EnsureInitializedForCurrentThread();
166
167 _callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback(
168 [weakSelf, runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) {
169 if (!uri.empty()) {
170 runner->PostTask([weakSelf, uri]() {
171 FlutterDartVMServicePublisher* strongSelf = weakSelf;
172 // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
173 // number.
174 if (strongSelf) {
175 NSURL* url =
176 [[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]];
177 strongSelf.url = url;
178 if (strongSelf.enableVMServicePublication) {
179 [[strongSelf delegate] publishServiceProtocolPort:url];
180 }
181 }
182 });
183 }
184 });
185
186 return self;
187 }
188
189 + (NSString*)serviceName {
190 return NSBundle.mainBundle.bundleIdentifier;
191 }
192
193 + (NSData*)createTxtData:(NSURL*)url {
194 // Check to see if there's an authentication code. If there is, we'll provide
195 // it as a txt record so flutter tools can establish a connection.
196 NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])];
197 NSData* pathData = [path dataUsingEncoding:NSUTF8StringEncoding];
198 NSDictionary<NSString*, NSData*>* txtDict = @{
199 @"authCode" : pathData,
200 };
201 return [NSNetService dataFromTXTRecordDictionary:txtDict];
202 }
203
204 - (void)dealloc {
205 [_delegate stopService];
206
207 flutter::DartServiceIsolate::RemoveServerStatusCallback(_callbackHandle);
208 }
209 @end
210
211 #endif // FLUTTER_RELEASE
FlutterDartVMServicePublisher.h
FlutterMacros.h
self
return self
Definition: FlutterTextureRegistryRelay.mm:19
DartVMServiceDNSServiceDelegate
Definition: FlutterDartVMServicePublisher.mm:65
FlutterDartVMServicePublisher
Definition: FlutterDartVMServicePublisher.h:11
FlutterDartVMServicePublisher::url
NSURL * url
Definition: FlutterDartVMServicePublisher.h:17
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
flutter
Definition: accessibility_bridge.h:26
fml
Definition: profiler_metrics_ios.mm:41
FlutterDartVMServicePublisherDelegate-p
Definition: FlutterDartVMServicePublisher.mm:50
-[FlutterDartVMServicePublisherDelegate-p stopService]
void stopService()