[ReactNative][RFC] Bring Chrome debugger to OSS. Part 1

This commit is contained in:
Alex Kotliarskyi 2015-03-19 09:55:40 -07:00
parent 1289536fe1
commit 0de49ab0d5
6 changed files with 2115 additions and 1 deletions

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; };
00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */; };
00D2771C1AB8C55500DC1E48 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D2771B1AB8C55500DC1E48 /* libicucore.dylib */; };
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; };
134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
134180021AA9153C003F314A /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FFF1AA91531003F314A /* libReactKit.a */; };
@ -30,6 +32,13 @@
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = UIExplorer;
};
00D277121AB8C2C700DC1E48 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
remoteInfo = RCTWebSocketDebugger;
};
13417FE71AA91428003F314A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
@ -92,6 +101,8 @@
004D289E1AAF61C70097A701 /* UIExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = "<group>"; };
00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = "<group>"; };
00D2771B1AB8C55500DC1E48 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = "<group>"; };
@ -121,6 +132,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
00D2771C1AB8C55500DC1E48 /* libicucore.dylib in Frameworks */,
00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */,
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
@ -152,6 +165,14 @@
name = "Supporting Files";
sourceTree = "<group>";
};
00D2770F1AB8C2C700DC1E48 /* Products */ = {
isa = PBXGroup;
children = (
00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */,
);
name = Products;
sourceTree = "<group>";
};
1316A21D1AA397F400C0188E /* Libraries */ = {
isa = PBXGroup;
children = (
@ -163,6 +184,8 @@
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */,
00D2771B1AB8C55500DC1E48 /* libicucore.dylib */,
);
name = Libraries;
sourceTree = "<group>";
@ -356,6 +379,10 @@
ProductGroup = D85B82921AB6D5CE003F4FE2 /* Products */;
ProjectRef = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */;
},
{
ProductGroup = 00D2770F1AB8C2C700DC1E48 /* Products */;
ProjectRef = 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */;
},
{
ProductGroup = 13417FFB1AA91531003F314A /* Products */;
ProjectRef = 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */;
@ -370,6 +397,13 @@
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTWebSocketDebugger.a;
remoteRef = 00D277121AB8C2C700DC1E48 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
13417FE81AA91428003F314A /* libRCTImage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -0,0 +1,9 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTJavaScriptExecutor.h"
@interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
- (instancetype)initWithURL:(NSURL *)url;
@end

View File

@ -0,0 +1,175 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTWebSocketExecutor.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#import "SRWebSocket.h"
typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
@interface RCTWebSocketExecutor () <SRWebSocketDelegate>
@end
@implementation RCTWebSocketExecutor {
SRWebSocket *_socket;
NSOperationQueue *_jsQueue;
NSMutableDictionary *_callbacks;
dispatch_semaphore_t _socketOpenSemaphore;
NSMutableDictionary *_injectedObjects;
}
- (instancetype)init
{
return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081"]];
}
- (instancetype)initWithURL:(NSURL *)url
{
if (self = [super init]) {
_jsQueue = [[NSOperationQueue alloc] init];
_jsQueue.maxConcurrentOperationCount = 1;
_socket = [[SRWebSocket alloc] initWithURL:url];
_socket.delegate = self;
_callbacks = [NSMutableDictionary dictionary];
_injectedObjects = [NSMutableDictionary dictionary];
[_socket setDelegateOperationQueue:_jsQueue];
if (![self connectToProxy]) {
RCTLogError(@"Connection to %@ timed out. Are you running node proxy?", url);
[self invalidate];
return nil;
}
NSInteger retries = 3;
BOOL runtimeIsReady = [self prepareJSRuntime];
while (!runtimeIsReady && retries > 0) {
runtimeIsReady = [self prepareJSRuntime];
retries--;
}
if (!runtimeIsReady) {
RCTLogError(@"Runtime is not ready. Do you have Chrome open?");
[self invalidate];
return nil;
}
}
return self;
}
- (BOOL)connectToProxy
{
_socketOpenSemaphore = dispatch_semaphore_create(0);
[_socket open];
long connected = dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2));
return connected == 0;
}
- (BOOL)prepareJSRuntime
{
__block NSError *initError;
dispatch_semaphore_t s = dispatch_semaphore_create(0);
[self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) {
initError = error;
dispatch_semaphore_signal(s);
}];
long runtimeIsReady = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC));
return runtimeIsReady == 0 && initError == nil;
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
NSError *error = nil;
NSDictionary *reply = RCTJSONParse(message, &error);
NSUInteger messageID = [reply[@"replyID"] integerValue];
WSMessageCallback callback = [_callbacks objectForKey:@(messageID)];
if (callback) {
callback(error, reply);
}
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
dispatch_semaphore_signal(_socketOpenSemaphore);
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
RCTLogError(@"WebSocket connection failed with error %@", error);
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
}
- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback
{
static NSUInteger lastID = 10000;
[_jsQueue addOperationWithBlock:^{
if (!self.valid) {
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{NSLocalizedDescriptionKey:@"socket closed"}];
callback(error, nil);
return;
}
NSUInteger expectedID = lastID++;
_callbacks[@(expectedID)] = [callback copy];
NSMutableDictionary *messageWithID = [message mutableCopy];
messageWithID[@"id"] = @(expectedID);
[_socket send:RCTJSONStringify(messageWithID, NULL)];
}];
}
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [url absoluteString], @"inject": _injectedObjects};
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
onComplete(error);
}];
}
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"moduleName": name, @"moduleMethod": method, @"arguments": arguments};
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
if (socketError) {
onComplete(nil, socketError);
return;
}
NSString *result = reply[@"result"];
id objcValue = RCTJSONParse(result, NULL);
onComplete(objcValue, nil);
}];
}
- (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete
{
[_jsQueue addOperationWithBlock:^{
[_injectedObjects setObject:script forKey:objectName];
onComplete(nil);
}];
}
- (void)invalidate
{
_socket.delegate = nil;
[_socket closeWithCode:1000 reason:@"Invalidated"];
_socket = nil;
}
- (BOOL)isValid
{
return _socket != nil && _socket.readyState == SR_OPEN;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
}
@end

View File

@ -0,0 +1,132 @@
//
// Copyright 2012 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <Security/SecCertificate.h>
typedef enum {
SR_CONNECTING = 0,
SR_OPEN = 1,
SR_CLOSING = 2,
SR_CLOSED = 3,
} SRReadyState;
typedef enum SRStatusCode : NSInteger {
SRStatusCodeNormal = 1000,
SRStatusCodeGoingAway = 1001,
SRStatusCodeProtocolError = 1002,
SRStatusCodeUnhandledType = 1003,
// 1004 reserved.
SRStatusNoStatusReceived = 1005,
// 1004-1006 reserved.
SRStatusCodeInvalidUTF8 = 1007,
SRStatusCodePolicyViolated = 1008,
SRStatusCodeMessageTooBig = 1009,
} SRStatusCode;
@class SRWebSocket;
extern NSString *const SRWebSocketErrorDomain;
extern NSString *const SRHTTPResponseErrorKey;
#pragma mark - SRWebSocketDelegate
@protocol SRWebSocketDelegate;
#pragma mark - SRWebSocket
@interface SRWebSocket : NSObject <NSStreamDelegate>
@property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
@property (nonatomic, readonly) SRReadyState readyState;
@property (nonatomic, readonly, retain) NSURL *url;
// This returns the negotiated protocol.
// It will be nil until after the handshake completes.
@property (nonatomic, readonly, copy) NSString *protocol;
// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol.
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols;
- (id)initWithURLRequest:(NSURLRequest *)request;
// Some helper constructors.
- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols;
- (id)initWithURL:(NSURL *)url;
// Delegate queue will be dispatch_main_queue by default.
// You cannot set both OperationQueue and dispatch_queue.
- (void)setDelegateOperationQueue:(NSOperationQueue*) queue;
- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue;
// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes.
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
// SRWebSockets are intended for one-time-use only. Open should be called once and only once.
- (void)open;
- (void)close;
- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
// Send a UTF8 String or Data.
- (void)send:(id)data;
// Send Data (can be nil) in a ping message.
- (void)sendPing:(NSData *)data;
@end
#pragma mark - SRWebSocketDelegate
@protocol SRWebSocketDelegate <NSObject>
// message will either be an NSString if the server is using text
// or NSData if the server is using binary.
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
@optional
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
@end
#pragma mark - NSURLRequest (CertificateAdditions)
@interface NSURLRequest (CertificateAdditions)
@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates;
@end
#pragma mark - NSMutableURLRequest (CertificateAdditions)
@interface NSMutableURLRequest (CertificateAdditions)
@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates;
@end
#pragma mark - NSRunLoop (SRWebSocket)
@interface NSRunLoop (SRWebSocket)
+ (NSRunLoop *)SR_networkRunLoop;
@end

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,10 @@ static Class _globalExecutorClass;
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_globalExecutorClass = [RCTWebViewExecutor class];
_globalExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!_globalExecutorClass) {
RCTLogWarn(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[self reloadAll];
}];