|
|
|
|
@ -4,18 +4,38 @@
|
|
|
|
|
|
|
|
|
|
#define FML_USED_ON_EMBEDDER
|
|
|
|
|
|
|
|
|
|
#import <TargetConditionals.h>
|
|
|
|
|
|
|
|
|
|
// NSNetService works fine on physical devices, but doesn't expose the services to regular mDNS
|
|
|
|
|
// queries on the Simulator. We can work around this by using the lower level C API, but that's
|
|
|
|
|
// only available from iOS 9.3+/macOS 10.11.4+.
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
#include <dns_sd.h> // nogncheck
|
|
|
|
|
#include <net/if.h> // nogncheck
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
|
|
|
|
|
#import "FlutterObservatoryPublisher.h"
|
|
|
|
|
|
|
|
|
|
#if FLUTTER_RELEASE
|
|
|
|
|
|
|
|
|
|
@implementation FlutterObservatoryPublisher
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#else // FLUTTER_RELEASE
|
|
|
|
|
|
|
|
|
|
#import <TargetConditionals.h>
|
|
|
|
|
// NSNetService works fine on physical devices before iOS 13.2.
|
|
|
|
|
// However, it doesn't expose the services to regular mDNS
|
|
|
|
|
// queries on the Simulator or on iOS 13.2+ devices.
|
|
|
|
|
//
|
|
|
|
|
// When debugging issues with this implementation, the following is helpful:
|
|
|
|
|
//
|
|
|
|
|
// 1) Running `dns-sd -Z _dartobservatory`. This is a built-in macOS tool that
|
|
|
|
|
// can find advertized observatories using this method. If dns-sd can't find
|
|
|
|
|
// it, then the observatory is not getting advertized over any network
|
|
|
|
|
// interface that the host machine has access to.
|
|
|
|
|
// 2) The Python zeroconf package. The dns-sd tool can sometimes see things
|
|
|
|
|
// that aren't advertizing over a network interface - for example, simulators
|
|
|
|
|
// using NSNetService has been observed using dns-sd, but doesn't show up in
|
|
|
|
|
// the Python package (which is a high quality socket based implementation).
|
|
|
|
|
// If that happens, this code should be tweaked such that it shows up in both
|
|
|
|
|
// dns-sd's output and Python zeroconf's detection.
|
|
|
|
|
// 3) The Dart multicast_dns package, which is what Flutter uses to find the
|
|
|
|
|
// port and auth code. If the advertizement shows up in dns-sd and Python
|
|
|
|
|
// zeroconf but not multicast_dns, then it is a bug in multicast_dns.
|
|
|
|
|
#include <dns_sd.h>
|
|
|
|
|
#include <net/if.h>
|
|
|
|
|
|
|
|
|
|
#include "flutter/fml/logging.h"
|
|
|
|
|
#include "flutter/fml/make_copyable.h"
|
|
|
|
|
#include "flutter/fml/memory/weak_ptr.h"
|
|
|
|
|
@ -24,136 +44,80 @@
|
|
|
|
|
#include "flutter/fml/task_runner.h"
|
|
|
|
|
#include "flutter/runtime/dart_service_isolate.h"
|
|
|
|
|
|
|
|
|
|
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \
|
|
|
|
|
FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DYNAMIC_RELEASE
|
|
|
|
|
@protocol FlutterObservatoryPublisherDelegate
|
|
|
|
|
- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner;
|
|
|
|
|
- (void)publishServiceProtocolPort:(NSString*)uri;
|
|
|
|
|
- (void)stopService;
|
|
|
|
|
|
|
|
|
|
@implementation FlutterObservatoryPublisher {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
@interface FlutterObservatoryPublisher () <NSNetServiceDelegate>
|
|
|
|
|
@property(readonly) fml::scoped_nsobject<NSURL> url;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FlutterObservatoryPublisher {
|
|
|
|
|
fml::scoped_nsobject<NSURL> _url;
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
@interface FlutterObservatoryPublisher ()
|
|
|
|
|
- (NSData*)createTxtData:(NSURL*)url;
|
|
|
|
|
|
|
|
|
|
@property(readonly) NSString* serviceName;
|
|
|
|
|
@property(readonly) fml::scoped_nsobject<NSObject<FlutterObservatoryPublisherDelegate>> delegate;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface ObservatoryNSNetServiceDelegate
|
|
|
|
|
: NSObject <FlutterObservatoryPublisherDelegate, NSNetServiceDelegate>
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface ObservatoryDNSServiceDelegate : NSObject <FlutterObservatoryPublisherDelegate>
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation ObservatoryDNSServiceDelegate {
|
|
|
|
|
fml::scoped_nsobject<FlutterObservatoryPublisher> _owner;
|
|
|
|
|
DNSServiceRef _dnsServiceRef;
|
|
|
|
|
#else // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
fml::scoped_nsobject<NSNetService> _netService;
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
|
|
|
|
|
flutter::DartServiceIsolate::CallbackHandle _callbackHandle;
|
|
|
|
|
std::unique_ptr<fml::WeakPtrFactory<FlutterObservatoryPublisher>> _weakFactory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSURL*)url {
|
|
|
|
|
return _url.get();
|
|
|
|
|
}
|
|
|
|
|
@synthesize url;
|
|
|
|
|
|
|
|
|
|
- (instancetype)init {
|
|
|
|
|
- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner {
|
|
|
|
|
self = [super init];
|
|
|
|
|
NSAssert(self, @"Super must not return null on init.");
|
|
|
|
|
|
|
|
|
|
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterObservatoryPublisher>>(self);
|
|
|
|
|
|
|
|
|
|
fml::MessageLoop::EnsureInitializedForCurrentThread();
|
|
|
|
|
|
|
|
|
|
_callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback(
|
|
|
|
|
[weak = _weakFactory->GetWeakPtr(),
|
|
|
|
|
runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) {
|
|
|
|
|
runner->PostTask([weak, uri]() {
|
|
|
|
|
if (weak) {
|
|
|
|
|
[weak.get() publishServiceProtocolPort:std::move(uri)];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_owner.reset([owner retain]);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)stopService {
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
if (_dnsServiceRef) {
|
|
|
|
|
DNSServiceRefDeallocate(_dnsServiceRef);
|
|
|
|
|
_dnsServiceRef = NULL;
|
|
|
|
|
}
|
|
|
|
|
#else // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
[_netService.get() stop];
|
|
|
|
|
[_netService.get() setDelegate:nil];
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
|
[self stopService];
|
|
|
|
|
|
|
|
|
|
flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle));
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)publishServiceProtocolPort:(std::string)uri {
|
|
|
|
|
[self stopService];
|
|
|
|
|
if (uri.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
- (void)publishServiceProtocolPort:(NSString*)uri {
|
|
|
|
|
// uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
|
|
|
|
|
// number.
|
|
|
|
|
_url.reset([[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]]);
|
|
|
|
|
url.reset([[[NSURL alloc] initWithString:uri] retain]);
|
|
|
|
|
|
|
|
|
|
NSString* serviceName =
|
|
|
|
|
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
|
|
|
|
|
|
|
|
|
|
// Check to see if there's an authentication code. If there is, we'll provide
|
|
|
|
|
// it as a txt record so flutter tools can establish a connection.
|
|
|
|
|
auto path = std::string{[[_url path] UTF8String]};
|
|
|
|
|
if (!path.empty()) {
|
|
|
|
|
// Remove leading "/"
|
|
|
|
|
path = path.substr(1);
|
|
|
|
|
}
|
|
|
|
|
NSData* pathData = [[[NSData alloc] initWithBytes:path.c_str() length:path.length()] autorelease];
|
|
|
|
|
NSDictionary* txtDict = @{
|
|
|
|
|
@"authCode" : pathData,
|
|
|
|
|
};
|
|
|
|
|
NSData* txtData = [NSNetService dataFromTXTRecordDictionary:txtDict];
|
|
|
|
|
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
DNSServiceFlags flags = kDNSServiceFlagsDefault;
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
// Simulator needs to use local loopback explicitly to work.
|
|
|
|
|
uint32_t interfaceIndex = if_nametoindex("lo0");
|
|
|
|
|
#else // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
// Physical devices need to request all interfaces.
|
|
|
|
|
uint32_t interfaceIndex = 0;
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
const char* registrationType = "_dartobservatory._tcp";
|
|
|
|
|
const char* domain = "local."; // default domain
|
|
|
|
|
uint16_t port = [[_url port] intValue];
|
|
|
|
|
uint16_t port = [[url port] unsignedShortValue];
|
|
|
|
|
|
|
|
|
|
int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, [serviceName UTF8String],
|
|
|
|
|
registrationType, domain, NULL, htons(port), txtData.length,
|
|
|
|
|
txtData.bytes, registrationCallback, NULL);
|
|
|
|
|
NSData* txtData = [_owner createTxtData:url.get()];
|
|
|
|
|
int err =
|
|
|
|
|
DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex,
|
|
|
|
|
[_owner.get().serviceName UTF8String], registrationType, domain, NULL,
|
|
|
|
|
htons(port), txtData.length, txtData.bytes, registrationCallback, NULL);
|
|
|
|
|
|
|
|
|
|
if (err != 0) {
|
|
|
|
|
FML_LOG(ERROR) << "Failed to register observatory port with mDNS.";
|
|
|
|
|
} else {
|
|
|
|
|
DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue());
|
|
|
|
|
}
|
|
|
|
|
#else // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local."
|
|
|
|
|
type:@"_dartobservatory._tcp."
|
|
|
|
|
name:serviceName
|
|
|
|
|
port:[[_url port] intValue]];
|
|
|
|
|
[netServiceTmp setTXTRecordData:txtData];
|
|
|
|
|
_netService.reset(netServiceTmp);
|
|
|
|
|
[_netService.get() setDelegate:self];
|
|
|
|
|
[_netService.get() publish];
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)netServiceDidPublish:(NSNetService*)sender {
|
|
|
|
|
FML_DLOG(INFO) << "FlutterObservatoryPublisher is ready!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)netService:(NSNetService*)sender didNotPublish:(NSDictionary*)errorDict {
|
|
|
|
|
FML_LOG(ERROR) << "Could not register as server for FlutterObservatoryPublisher. Check your "
|
|
|
|
|
"network settings and relaunch the application.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
static void DNSSD_API registrationCallback(DNSServiceRef sdRef,
|
|
|
|
|
DNSServiceFlags flags,
|
|
|
|
|
DNSServiceErrorType errorCode,
|
|
|
|
|
@ -168,9 +132,113 @@ static void DNSSD_API registrationCallback(DNSServiceRef sdRef,
|
|
|
|
|
"network settings and relaunch the application.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
|
|
|
|
|
|
#endif // FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE && FLUTTER_RUNTIME_MODE !=
|
|
|
|
|
// FLUTTER_RUNTIME_MODE_DYNAMIC_RELEASE
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation ObservatoryNSNetServiceDelegate {
|
|
|
|
|
fml::scoped_nsobject<FlutterObservatoryPublisher> _owner;
|
|
|
|
|
fml::scoped_nsobject<NSNetService> _netService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@synthesize url;
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner {
|
|
|
|
|
self = [super init];
|
|
|
|
|
NSAssert(self, @"Super must not return null on init.");
|
|
|
|
|
_owner.reset([owner retain]);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)stopService {
|
|
|
|
|
[_netService.get() stop];
|
|
|
|
|
[_netService.get() setDelegate:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)publishServiceProtocolPort:(NSString*)uri {
|
|
|
|
|
// uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
|
|
|
|
|
// number.
|
|
|
|
|
url.reset([[[NSURL alloc] initWithString:uri] retain]);
|
|
|
|
|
|
|
|
|
|
NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local."
|
|
|
|
|
type:@"_dartobservatory._tcp."
|
|
|
|
|
name:_owner.get().serviceName
|
|
|
|
|
port:[[url port] intValue]];
|
|
|
|
|
[netServiceTmp setTXTRecordData:[_owner createTxtData:url.get()]];
|
|
|
|
|
_netService.reset(netServiceTmp);
|
|
|
|
|
[_netService.get() setDelegate:self];
|
|
|
|
|
[_netService.get() publish];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)netServiceDidPublish:(NSNetService*)sender {
|
|
|
|
|
FML_DLOG(INFO) << "FlutterObservatoryPublisher is ready!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)netService:(NSNetService*)sender didNotPublish:(NSDictionary*)errorDict {
|
|
|
|
|
FML_LOG(ERROR) << "Could not register as server for FlutterObservatoryPublisher. Check your "
|
|
|
|
|
"network settings and relaunch the application.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FlutterObservatoryPublisher {
|
|
|
|
|
flutter::DartServiceIsolate::CallbackHandle _callbackHandle;
|
|
|
|
|
std::unique_ptr<fml::WeakPtrFactory<FlutterObservatoryPublisher>> _weakFactory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSURL*)url {
|
|
|
|
|
return [_delegate.get().url autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (instancetype)init {
|
|
|
|
|
self = [super init];
|
|
|
|
|
NSAssert(self, @"Super must not return null on init.");
|
|
|
|
|
|
|
|
|
|
if (@available(iOS 9.3, *)) {
|
|
|
|
|
_delegate.reset([[ObservatoryDNSServiceDelegate alloc] initWithOwner:self]);
|
|
|
|
|
} else {
|
|
|
|
|
_delegate.reset([[ObservatoryNSNetServiceDelegate alloc] initWithOwner:self]);
|
|
|
|
|
}
|
|
|
|
|
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterObservatoryPublisher>>(self);
|
|
|
|
|
|
|
|
|
|
fml::MessageLoop::EnsureInitializedForCurrentThread();
|
|
|
|
|
|
|
|
|
|
_callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback(
|
|
|
|
|
[weak = _weakFactory->GetWeakPtr(),
|
|
|
|
|
runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) {
|
|
|
|
|
if (!uri.empty()) {
|
|
|
|
|
runner->PostTask([weak, uri]() {
|
|
|
|
|
if (weak) {
|
|
|
|
|
[[weak.get() delegate]
|
|
|
|
|
publishServiceProtocolPort:[NSString stringWithUTF8String:uri.c_str()]];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*)serviceName {
|
|
|
|
|
return NSBundle.mainBundle.bundleIdentifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSData*)createTxtData:(NSURL*)url {
|
|
|
|
|
// Check to see if there's an authentication code. If there is, we'll provide
|
|
|
|
|
// it as a txt record so flutter tools can establish a connection.
|
|
|
|
|
NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])];
|
|
|
|
|
NSData* pathData = [path dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
|
NSDictionary<NSString*, NSData*>* txtDict = @{
|
|
|
|
|
@"authCode" : pathData,
|
|
|
|
|
};
|
|
|
|
|
return [NSNetService dataFromTXTRecordDictionary:txtDict];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
|
[_delegate stopService];
|
|
|
|
|
|
|
|
|
|
flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle));
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#endif // FLUTTER_RELEASE
|
|
|
|
|
|