• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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