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 <TargetConditionals.h> 8 9// NSNetService works fine on physical devices, but doesn't expose the services to regular mDNS 10// queries on the Simulator. We can work around this by using the lower level C API, but that's 11// only available from iOS 9.3+/macOS 10.11.4+. 12#if TARGET_IPHONE_SIMULATOR 13#include <dns_sd.h> // nogncheck 14#include <net/if.h> // nogncheck 15#endif // TARGET_IPHONE_SIMULATOR 16 17#import "FlutterObservatoryPublisher.h" 18 19#include "flutter/fml/logging.h" 20#include "flutter/fml/make_copyable.h" 21#include "flutter/fml/memory/weak_ptr.h" 22#include "flutter/fml/message_loop.h" 23#include "flutter/fml/platform/darwin/scoped_nsobject.h" 24#include "flutter/fml/task_runner.h" 25#include "flutter/runtime/dart_service_isolate.h" 26 27#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \ 28 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DYNAMIC_RELEASE 29 30@implementation FlutterObservatoryPublisher { 31} 32 33#else 34 35@interface FlutterObservatoryPublisher () <NSNetServiceDelegate> 36@end 37 38@implementation FlutterObservatoryPublisher { 39 fml::scoped_nsobject<NSURL> _url; 40#if TARGET_IPHONE_SIMULATOR 41 DNSServiceRef _dnsServiceRef; 42#else // TARGET_IPHONE_SIMULATOR 43 fml::scoped_nsobject<NSNetService> _netService; 44#endif // TARGET_IPHONE_SIMULATOR 45 46 flutter::DartServiceIsolate::CallbackHandle _callbackHandle; 47 std::unique_ptr<fml::WeakPtrFactory<FlutterObservatoryPublisher>> _weakFactory; 48} 49 50- (NSURL*)url { 51 return _url.get(); 52} 53 54- (instancetype)init { 55 self = [super init]; 56 NSAssert(self, @"Super must not return null on init."); 57 58 _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterObservatoryPublisher>>(self); 59 60 fml::MessageLoop::EnsureInitializedForCurrentThread(); 61 62 _callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback( 63 [weak = _weakFactory->GetWeakPtr(), 64 runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) { 65 runner->PostTask([weak, uri]() { 66 if (weak) { 67 [weak.get() publishServiceProtocolPort:std::move(uri)]; 68 } 69 }); 70 }); 71 72 return self; 73} 74 75- (void)stopService { 76#if TARGET_IPHONE_SIMULATOR 77 if (_dnsServiceRef) { 78 DNSServiceRefDeallocate(_dnsServiceRef); 79 _dnsServiceRef = NULL; 80 } 81#else // TARGET_IPHONE_SIMULATOR 82 [_netService.get() stop]; 83#endif // TARGET_IPHONE_SIMULATOR 84} 85 86- (void)dealloc { 87 [self stopService]; 88 89 flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle)); 90 [super dealloc]; 91} 92 93- (void)publishServiceProtocolPort:(std::string)uri { 94 [self stopService]; 95 if (uri.empty()) { 96 return; 97 } 98 // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port 99 // number. 100 _url.reset([[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]]); 101 102 NSString* serviceName = 103 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]; 104 105 // Check to see if there's an authentication code. If there is, we'll provide 106 // it as a txt record so flutter tools can establish a connection. 107 auto path = std::string{[[_url path] UTF8String]}; 108 if (!path.empty()) { 109 // Remove leading "/" 110 path = path.substr(1); 111 } 112 NSData* pathData = [[[NSData alloc] initWithBytes:path.c_str() length:path.length()] autorelease]; 113 NSDictionary* txtDict = @{ 114 @"authCode" : pathData, 115 }; 116 NSData* txtData = [NSNetService dataFromTXTRecordDictionary:txtDict]; 117 118#if TARGET_IPHONE_SIMULATOR 119 DNSServiceFlags flags = kDNSServiceFlagsDefault; 120 uint32_t interfaceIndex = if_nametoindex("lo0"); 121 const char* registrationType = "_dartobservatory._tcp"; 122 const char* domain = "local."; // default domain 123 uint16_t port = [[_url port] intValue]; 124 125 int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, [serviceName UTF8String], 126 registrationType, domain, NULL, htons(port), txtData.length, 127 txtData.bytes, registrationCallback, NULL); 128 129 if (err != 0) { 130 FML_LOG(ERROR) << "Failed to register observatory port with mDNS."; 131 } else { 132 DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue()); 133 } 134#else // TARGET_IPHONE_SIMULATOR 135 NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local." 136 type:@"_dartobservatory._tcp." 137 name:serviceName 138 port:[[_url port] intValue]]; 139 [netServiceTmp setTXTRecordData:txtData]; 140 _netService.reset(netServiceTmp); 141 [_netService.get() setDelegate:self]; 142 [_netService.get() publish]; 143#endif // TARGET_IPHONE_SIMULATOR 144} 145 146- (void)netServiceDidPublish:(NSNetService*)sender { 147 FML_DLOG(INFO) << "FlutterObservatoryPublisher is ready!"; 148} 149 150- (void)netService:(NSNetService*)sender didNotPublish:(NSDictionary*)errorDict { 151 FML_LOG(ERROR) << "Could not register as server for FlutterObservatoryPublisher. Check your " 152 "network settings and relaunch the application."; 153} 154 155#if TARGET_IPHONE_SIMULATOR 156static void DNSSD_API registrationCallback(DNSServiceRef sdRef, 157 DNSServiceFlags flags, 158 DNSServiceErrorType errorCode, 159 const char* name, 160 const char* regType, 161 const char* domain, 162 void* context) { 163 if (errorCode == kDNSServiceErr_NoError) { 164 FML_DLOG(INFO) << "FlutterObservatoryPublisher is ready!"; 165 } else { 166 FML_LOG(ERROR) << "Could not register as server for FlutterObservatoryPublisher. Check your " 167 "network settings and relaunch the application."; 168 } 169} 170#endif // TARGET_IPHONE_SIMULATOR 171 172#endif // FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE && FLUTTER_RUNTIME_MODE != 173 // FLUTTER_RUNTIME_MODE_DYNAMIC_RELEASE 174 175@end 176