Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Sonar/SonarConnection.h>
#import <SonarKit/SonarConnection.h>
/**
SonarCppBridgingConnection is a simple ObjC wrapper around SonarConnection
that forwards messages to the underlying C++ connection. This class allows
pure Objective-C plugins to send messages to the underlying connection.
*/
@interface SonarCppBridgingConnection : NSObject <SonarConnection>
- (instancetype)initWithCppConnection:(std::shared_ptr<facebook::sonar::SonarConnection>)conn;
@end

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import "SonarCppBridgingConnection.h"
#import <FBCxxUtils/FBCxxFollyDynamicConvert.h>
#import "SonarCppBridgingResponder.h"
@implementation SonarCppBridgingConnection
{
std::shared_ptr<facebook::sonar::SonarConnection> conn_;
}
- (instancetype)initWithCppConnection:(std::shared_ptr<facebook::sonar::SonarConnection>)conn
{
if (self = [super init]) {
conn_ = conn;
}
return self;
}
#pragma mark - SonarConnection
- (void)send:(NSString *)method withParams:(NSDictionary *)params
{
conn_->send([method UTF8String], facebook::cxxutils::convertIdToFollyDynamic(params));
}
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver
{
const auto lambda = [receiver](const folly::dynamic &message,
std::unique_ptr<facebook::sonar::SonarResponder> responder) {
SonarCppBridgingResponder *const objCResponder =
[[SonarCppBridgingResponder alloc] initWithCppResponder:std::move(responder)];
receiver(facebook::cxxutils::convertFollyDynamicToId(message), objCResponder);
};
conn_->receive([method UTF8String], lambda);
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Sonar/SonarResponder.h>
#import <SonarKit/SonarResponder.h>
/**
SonarCppBridgingResponder is a simple ObjC wrapper around SonarResponder
that forwards messages to the underlying C++ responder. This class allows
pure Objective-C plugins to send messages to the underlying responder.
*/
@interface SonarCppBridgingResponder : NSObject <SonarResponder>
- (instancetype)initWithCppResponder:(std::unique_ptr<facebook::sonar::SonarResponder>)responder;
@end

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import "SonarCppBridgingResponder.h"
#import <FBCxxUtils/FBCxxFollyDynamicConvert.h>
@implementation SonarCppBridgingResponder {
std::unique_ptr<facebook::sonar::SonarResponder> responder_;
}
- (instancetype)initWithCppResponder:(std::unique_ptr<facebook::sonar::SonarResponder>)responder
{
if (!responder) {
return nil;
}
if (self = [super init]) {
responder_ = std::move(responder);
}
return self;
}
#pragma mark - SonarResponder
- (void)success:(NSDictionary *)response { responder_->success(facebook::cxxutils::convertIdToFollyDynamic(response)); }
- (void)error:(NSDictionary *)response { responder_->error(facebook::cxxutils::convertIdToFollyDynamic(response)); }
@end

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#ifndef __OBJC__
#error This header can only be included in .mm (ObjC++) files
#endif
#import <Sonar/SonarPlugin.h>
#import <SonarKit/SonarCppBridgingConnection.h>
#import <SonarKit/SonarPlugin.h>
namespace facebook {
namespace sonar {
using ObjCPlugin = NSObject<SonarPlugin> *;
/**
SonarCppWrapperPlugin is a simple C++ wrapper around Objective-C Sonar plugins
that can be passed to SonarClient. This class allows developers to write pure
Objective-C plugins if they want.
*/
class SonarCppWrapperPlugin final : public facebook::sonar::SonarPlugin {
public:
// Under ARC copying objCPlugin *does* increment its retain count
SonarCppWrapperPlugin(ObjCPlugin objCPlugin) : _objCPlugin(objCPlugin) {}
std::string identifier() const override { return [[_objCPlugin identifier] UTF8String]; }
void didConnect(std::shared_ptr<facebook::sonar::SonarConnection> conn) override
{
SonarCppBridgingConnection *const bridgingConn = [[SonarCppBridgingConnection alloc] initWithCppConnection:conn];
[_objCPlugin didConnect:bridgingConn];
}
void didDisconnect() override { [_objCPlugin didDisconnect]; }
ObjCPlugin getObjCPlugin() { return _objCPlugin; }
private:
ObjCPlugin _objCPlugin;
};
} // namespace sonar
} // namespace facebook

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#ifndef FB_SK_MACROS_H
#define FB_SK_MACROS_H
#define FB_LINK_REQUIRE_(NAME, UNIQUE)
#define FB_LINKABLE(NAME)
#define FB_LINK_REQUIRE(NAME)
#define FB_LINK_REQUIRE_EXT(NAME)
#endif

27
iOS/SonarKit/SKMacros.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#ifndef SKMACROS_H
#define SKMACROS_H
#import <FBDefines/FBMacros.h>
#ifdef __cplusplus
# define SK_EXTERN_C_BEGIN extern "C" {
# define SK_EXTERN_C_END }
# define SK_EXTERN_C extern "C"
#else
# define SK_EXTERN_C_BEGIN
# define SK_EXTERN_C_END
# define SK_EXTERN_C extern
#endif
#define SKLog(...) NSLog(__VA_ARGS__)
#define SKTrace(...) /*NSLog(__VA_ARGS__)*/
#endif

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
#import "SonarPlugin.h"
/**
Represents a connection between the Sonar desktop och client side. Manages the lifecycle of attached
plugin instances.
*/
@interface SonarClient : NSObject
/**
The shared singleton SonarClient instance. It is an error to call this on non-debug builds to avoid leaking data.
*/
+ (instancetype)sharedClient;
/**
Register a plugin with the client.
*/
- (void)addPlugin:(NSObject<SonarPlugin> *)plugin;
/**
Unregister a plugin with the client.
*/
- (void)removePlugin:(NSObject<SonarPlugin> *)plugin;
/**
Retrieve the plugin with a given identifier which was previously registered with this client.
*/
- (NSObject<SonarPlugin> *)pluginWithIdentifier:(NSString *)identifier;
/**
Establish a connection to the Sonar desktop.
*/
- (void)start;
/**
Stop the connection to the Sonar desktop.
*/
- (void)stop;
// initializers are disabled. You must use `+[SonarClient sharedClient]` instance.
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end

117
iOS/SonarKit/SonarClient.mm Normal file
View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#if FB_SONARKIT_ENABLED
#import "SonarClient.h"
#import "SonarCppWrapperPlugin.h"
#import <Sonar/SonarClient.h>
#include <folly/io/async/EventBase.h>
#include <folly/io/async/ScopedEventBaseThread.h>
#import <UIKit/UIKit.h>
#if !TARGET_OS_SIMULATOR
#import "SKPortForwardingServer.h"
#endif
using WrapperPlugin = facebook::sonar::SonarCppWrapperPlugin;
@implementation SonarClient {
facebook::sonar::SonarClient *_cppClient;
folly::ScopedEventBaseThread eventBaseThread;
#if !TARGET_OS_SIMULATOR
SKPortForwardingServer *_server;
#endif
}
+ (instancetype)sharedClient
{
static SonarClient *sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[self alloc] init];
});
return sharedClient;
}
- (instancetype)init
{
if (self = [super init]) {
UIDevice *device = [UIDevice currentDevice];
NSString *deviceName = [device name];
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
NSString *deviceId = [[device identifierForVendor]UUIDString];
NSString *appId = appName;
NSString *privateAppDirectory = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
#if TARGET_OS_SIMULATOR
deviceName = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] model], @"Simulator"];
#endif
facebook::sonar::SonarClient::init({
{
"localhost",
"iOS",
[deviceName UTF8String],
[deviceId UTF8String],
[appName UTF8String],
[appId UTF8String],
[privateAppDirectory UTF8String],
},
eventBaseThread.getEventBase(),
});
_cppClient = facebook::sonar::SonarClient::instance();
}
return self;
}
- (void)refreshPlugins
{
_cppClient->refreshPlugins();
}
- (void)addPlugin:(NSObject<SonarPlugin> *)plugin
{
_cppClient->addPlugin(std::make_shared<WrapperPlugin>(plugin));
}
- (void)removePlugin:(NSObject<SonarPlugin> *)plugin
{
_cppClient->removePlugin(std::make_shared<WrapperPlugin>(plugin));
}
- (NSObject<SonarPlugin> *)pluginWithIdentifier:(NSString *)identifier
{
auto cppPlugin = _cppClient->getPlugin([identifier UTF8String]);
if (auto wrapper = dynamic_cast<WrapperPlugin *>(cppPlugin.get())) {
return wrapper->getObjCPlugin();
}
return nil;
}
- (void)start;
{
#if !TARGET_OS_SIMULATOR
_server = [SKPortForwardingServer new];
[_server forwardConnectionsFromPort:8088];
[_server listenForMultiplexingChannelOnPort:8078];
#endif
_cppClient->start();
}
- (void)stop
{
_cppClient->stop();
#if !TARGET_OS_SIMULATOR
[_server close];
_server = nil;
#endif
}
@end
#endif

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
@protocol SonarResponder;
@protocol SonarWebSocket;
typedef void (^SonarReceiver)(NSDictionary*, id<SonarResponder>);
/**
Represents a connection between the Desktop and mobile plugins with corresponding identifiers.
*/
@protocol SonarConnection
/**
Invoke a method on the Sonar desktop plugin with with a matching identifier.
*/
- (void)send:(NSString *)method withParams:(NSDictionary *)params;
/**
Register a receiver to be notified of incoming calls of the given method from the Sonar desktop
plugin with a matching identifier.
*/
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver;
@end

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
#import "SKMacros.h"
SK_EXTERN_C_BEGIN
void SonarPerformBlockOnMainThread(void(^block)());
SK_EXTERN_C_END
@protocol SonarConnection;
@protocol SonarPlugin
/**
The plugin's identifier. This should map to a javascript plugin with the same identifier to ensure
messages are sent correctly.
*/
- (NSString *)identifier;
/**
Called when a connection has been established between this plugin and the corresponding plugin on
the Sonar desktop app. The provided connection can be used to register method receivers as well
as send messages back to the desktop app.
*/
- (void)didConnect:(id<SonarConnection>)connection;
/**
Called when a plugin has been disconnected and the SonarConnection provided in didConnect is no
longer valid to use.
*/
- (void)didDisconnect;
@end

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
/**
Acts as a hook for providing return values to remote called from Sonar desktop plugins.
*/
@protocol SonarResponder
/**
Respond with a successful return value.
*/
- (void)success:(NSDictionary *)response;
/**
Respond with an error.
*/
- (void)error:(NSDictionary *)response;
@end

18
iOS/SonarKit/SonarUtil.m Normal file
View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
#import "SonarPlugin.h"
void SonarPerformBlockOnMainThread(void(^block)())
{
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
enum {
SKPortForwardingFrameTypeOpenPipe = 201,
SKPortForwardingFrameTypeWriteToPipe = 202,
SKPortForwardingFrameTypeClosePipe = 203,
};
static dispatch_data_t NSDataToGCDData(NSData *data) {
__block NSData *retainedData = data;
return dispatch_data_create(data.bytes, data.length, nil, ^{
retainedData = nil;
});
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
@interface SKPortForwardingServer : NSObject
- (instancetype)init;
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port;
- (void)forwardConnectionsFromPort:(NSUInteger)port;
- (void)close;
@end

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import "SKPortForwardingServer.h"
#import <UIKit/UIKit.h>
#import <CocoaAsyncSocket/GCDAsyncSocket.h>
#import <peertalk/PTChannel.h>
#import "SKMacros.h"
#import "SKPortForwardingCommon.h"
@interface SKPortForwardingServer () <PTChannelDelegate, GCDAsyncSocketDelegate>
@property (nonatomic, weak) PTChannel *serverChannel;
@property (nonatomic, weak) PTChannel *peerChannel;
@property (nonatomic, strong) GCDAsyncSocket *serverSocket;
@property (nonatomic, strong) NSMutableDictionary *clientSockets;
@property (nonatomic, assign) UInt32 lastClientSocketTag;
@property (nonatomic, strong) dispatch_queue_t socketQueue;
@property (nonatomic, strong) PTProtocol *protocol;
@end
@implementation SKPortForwardingServer
- (instancetype)init
{
if (self = [super init]) {
_socketQueue = dispatch_queue_create("SKPortForwardingServer", DISPATCH_QUEUE_SERIAL);
_lastClientSocketTag = 0;
_clientSockets = [NSMutableDictionary dictionary];
_protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue];
}
return self;
}
- (void)dealloc
{
[self close];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)forwardConnectionsFromPort:(NSUInteger)port
{
[self _forwardConnectionsFromPort:port reportError:YES];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
[self _forwardConnectionsFromPort:port reportError:NO];
}];
}
- (void)_forwardConnectionsFromPort:(NSUInteger)port reportError:(BOOL)shouldReportError
{
GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue];
NSError *listenError;
if ([serverSocket acceptOnPort:port error:&listenError]) {
self.serverSocket = serverSocket;
} else {
if (shouldReportError) {
SKLog(@"Failed to listen: %@", listenError);
}
}
}
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port
{
[self _listenForMultiplexingChannelOnPort:port reportError:YES];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
[self _listenForMultiplexingChannelOnPort:port reportError:NO];
}];
}
- (void)_listenForMultiplexingChannelOnPort:(NSUInteger)port reportError:(BOOL)shouldReportError
{
PTChannel *channel = [[PTChannel alloc] initWithProtocol:_protocol delegate:self];
[channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
if (error) {
if (shouldReportError) {
SKLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error);
}
} else {
SKTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port);
self.serverChannel = channel;
}
}];
}
- (void)close
{
if (self.serverChannel) {
[self.serverChannel close];
self.serverChannel = nil;
}
[self.serverSocket disconnect];
}
#pragma mark - PTChannelDelegate
- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address {
// Cancel any other connection. We are FIFO, so the last connection
// established will cancel any previous connection and "take its place".
if (self.peerChannel) {
[self.peerChannel cancel];
}
// Weak pointer to current connection. Connection objects live by themselves
// (owned by its parent dispatch queue) until they are closed.
self.peerChannel = otherChannel;
self.peerChannel.userInfo = address;
SKTrace(@"Connected to %@", address);
}
- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload {
//NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload);
if (type == SKPortForwardingFrameTypeWriteToPipe) {
GCDAsyncSocket *sock = self.clientSockets[@(tag)];
[sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0];
SKTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length);
}
if (type == SKPortForwardingFrameTypeClosePipe) {
GCDAsyncSocket *sock = self.clientSockets[@(tag)];
[sock disconnectAfterWriting];
}
}
- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error {
for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) {
[sock setDelegate:nil];
[sock disconnect];
}
[self.clientSockets removeAllObjects];
SKTrace(@"Disconnected from %@, error = %@", channel.userInfo, error);
}
#pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
dispatch_block_t block = ^() {
if (!self.peerChannel) {
[newSocket setDelegate:nil];
[newSocket disconnect];
}
UInt32 tag = ++self->_lastClientSocketTag;
newSocket.userData = @(tag);
newSocket.delegate = self;
self.clientSockets[@(tag)] = newSocket;
[self.peerChannel sendFrameOfType:SKPortForwardingFrameTypeOpenPipe tag:self->_lastClientSocketTag withPayload:nil callback:^(NSError *error) {
SKTrace(@"open socket (%d), error = %@", (unsigned int)tag, error);
[newSocket readDataWithTimeout:-1 tag:0];
}];
};
if (_peerChannel) {
block();
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), _socketQueue, block);
}
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_
{
UInt32 tag = [[sock userData] unsignedIntValue];
SKTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length);
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) {
SKTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error);
[sock readDataWithTimeout:-1 tag:_];
}];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
UInt32 tag = [sock.userData unsignedIntValue];
[_clientSockets removeObjectForKey:@(tag)];
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) {
SKTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error);
}];
}
@end