2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-03-19 16:55:40 +00:00
|
|
|
|
|
|
|
#import "RCTWebSocketExecutor.h"
|
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
#import <React/RCTAssert.h>
|
|
|
|
#import <React/RCTBridge.h>
|
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import <React/RCTDefines.h>
|
|
|
|
#import <React/RCTLog.h>
|
|
|
|
#import <React/RCTUtils.h>
|
|
|
|
|
2015-05-14 16:28:09 +00:00
|
|
|
#import "RCTSRWebSocket.h"
|
2015-03-19 16:55:40 +00:00
|
|
|
|
2016-11-21 15:02:58 +00:00
|
|
|
#if RCT_DEV // Debug executors are only supported in dev mode
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary<NSString *, id> *reply);
|
2015-05-14 16:28:09 +00:00
|
|
|
|
|
|
|
@interface RCTWebSocketExecutor () <RCTSRWebSocketDelegate>
|
2015-03-19 16:55:40 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
@implementation RCTWebSocketExecutor
|
|
|
|
{
|
2015-05-14 16:28:09 +00:00
|
|
|
RCTSRWebSocket *_socket;
|
2015-04-11 22:08:00 +00:00
|
|
|
dispatch_queue_t _jsQueue;
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSNumber *, RCTWSMessageCallback> *_callbacks;
|
2015-03-19 16:55:40 +00:00
|
|
|
dispatch_semaphore_t _socketOpenSemaphore;
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSString *, NSString *> *_injectedObjects;
|
2015-06-09 22:42:10 +00:00
|
|
|
NSURL *_url;
|
2016-11-21 15:02:58 +00:00
|
|
|
NSError *_setupError;
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
2015-06-09 22:42:10 +00:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2016-06-13 20:15:30 +00:00
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
- (instancetype)initWithURL:(NSURL *)URL
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
RCTAssertParam(URL);
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
if ((self = [self init])) {
|
2015-06-09 22:42:10 +00:00
|
|
|
_url = URL;
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-06-09 22:42:10 +00:00
|
|
|
- (void)setUp
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_url) {
|
|
|
|
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
|
2016-10-12 15:36:37 +00:00
|
|
|
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
// TODO t16297016: this seems to be unused, remove?
|
2016-10-12 15:36:37 +00:00
|
|
|
NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"];
|
|
|
|
if (!port) {
|
|
|
|
port = [[[_bridge bundleURL] port] integerValue] ?: 8081;
|
|
|
|
}
|
|
|
|
|
2016-06-13 20:15:30 +00:00
|
|
|
NSString *host = [[_bridge bundleURL] host];
|
|
|
|
if (!host) {
|
|
|
|
host = @"localhost";
|
|
|
|
}
|
|
|
|
NSString *URLString = [NSString stringWithFormat:@"http://%@:%zd/debugger-proxy?role=client", host, port];
|
2015-11-25 11:09:00 +00:00
|
|
|
_url = [RCTConvert NSURL:URLString];
|
|
|
|
}
|
|
|
|
|
2016-04-01 14:01:51 +00:00
|
|
|
_jsQueue = dispatch_queue_create("com.facebook.react.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
|
2015-06-09 22:42:10 +00:00
|
|
|
_socket = [[RCTSRWebSocket alloc] initWithURL:_url];
|
|
|
|
_socket.delegate = self;
|
2015-11-14 18:25:00 +00:00
|
|
|
_callbacks = [NSMutableDictionary new];
|
2015-08-17 14:35:34 +00:00
|
|
|
_injectedObjects = [NSMutableDictionary new];
|
2015-06-09 22:42:10 +00:00
|
|
|
[_socket setDelegateDispatchQueue:_jsQueue];
|
|
|
|
|
2016-04-07 20:14:39 +00:00
|
|
|
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-js-devtools" relativeToURL:_url];
|
2015-06-09 22:42:10 +00:00
|
|
|
|
2016-09-27 13:19:45 +00:00
|
|
|
NSURLSession *session = [NSURLSession sharedSession];
|
|
|
|
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:startDevToolsURL]
|
|
|
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){}];
|
|
|
|
[dataTask resume];
|
2015-06-09 22:42:10 +00:00
|
|
|
if (![self connectToProxy]) {
|
|
|
|
[self invalidate];
|
2016-11-21 15:02:58 +00:00
|
|
|
NSString *error = [NSString stringWithFormat:@"Connection to %@ timed out. Are you "
|
|
|
|
"running node proxy? If you are running on the device, check if "
|
|
|
|
"you have the right IP address in `RCTWebSocketExecutor.m`.", _url];
|
|
|
|
_setupError = RCTErrorWithMessage(error);
|
|
|
|
RCTFatal(_setupError);
|
2015-06-09 22:42:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSInteger retries = 3;
|
|
|
|
BOOL runtimeIsReady = [self prepareJSRuntime];
|
|
|
|
while (!runtimeIsReady && retries > 0) {
|
|
|
|
runtimeIsReady = [self prepareJSRuntime];
|
|
|
|
retries--;
|
|
|
|
}
|
|
|
|
if (!runtimeIsReady) {
|
|
|
|
[self invalidate];
|
2016-11-21 15:02:58 +00:00
|
|
|
NSString *error = @"Runtime is not ready for debugging.\n "
|
|
|
|
"- Make sure Packager server is running.\n"
|
|
|
|
"- Make sure the JavaScript Debugger is running and not paused on a "
|
|
|
|
"breakpoint or exception and try reloading again.";
|
|
|
|
_setupError = RCTErrorWithMessage(error);
|
|
|
|
RCTFatal(_setupError);
|
2015-06-09 22:42:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-19 16:55:40 +00:00
|
|
|
- (BOOL)connectToProxy
|
|
|
|
{
|
|
|
|
_socketOpenSemaphore = dispatch_semaphore_create(0);
|
|
|
|
[_socket open];
|
2016-04-20 00:12:24 +00:00
|
|
|
long connected = dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10));
|
2016-11-21 15:02:58 +00:00
|
|
|
return connected == 0 && _socket.readyState == RCTSR_OPEN;
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)prepareJSRuntime
|
|
|
|
{
|
|
|
|
__block NSError *initError;
|
|
|
|
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
2016-11-21 15:02:58 +00:00
|
|
|
[self sendMessage:@{@"method": @"prepareJSRuntime"} onReply:^(NSError *error, NSDictionary<NSString *, id> *reply) {
|
2015-03-19 16:55:40 +00:00
|
|
|
initError = error;
|
|
|
|
dispatch_semaphore_signal(s);
|
|
|
|
}];
|
|
|
|
long runtimeIsReady = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC));
|
2016-11-21 15:02:58 +00:00
|
|
|
if (initError) {
|
|
|
|
RCTLogInfo(@"Websocket runtime setup failed: %@", initError);
|
|
|
|
}
|
2015-03-19 16:55:40 +00:00
|
|
|
return runtimeIsReady == 0 && initError == nil;
|
|
|
|
}
|
|
|
|
|
2015-05-14 16:28:09 +00:00
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
|
|
|
NSError *error = nil;
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, id> *reply = RCTJSONParse(message, &error);
|
2015-08-21 17:15:04 +00:00
|
|
|
NSNumber *messageID = reply[@"replyID"];
|
|
|
|
RCTWSMessageCallback callback = _callbacks[messageID];
|
|
|
|
if (callback) {
|
|
|
|
callback(error, reply);
|
2016-12-08 01:11:57 +00:00
|
|
|
[_callbacks removeObjectForKey:messageID];
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-14 16:28:09 +00:00
|
|
|
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
|
|
|
dispatch_semaphore_signal(_socketOpenSemaphore);
|
|
|
|
}
|
|
|
|
|
2015-05-14 16:28:09 +00:00
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
2015-12-23 21:31:44 +00:00
|
|
|
dispatch_semaphore_signal(_socketOpenSemaphore);
|
2016-11-21 15:02:58 +00:00
|
|
|
RCTLogInfo(@"WebSocket connection failed with error %@", error);
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
2016-11-21 15:02:58 +00:00
|
|
|
- (void)sendMessage:(NSDictionary<NSString *, id> *)message onReply:(RCTWSMessageCallback)callback
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
|
|
|
static NSUInteger lastID = 10000;
|
|
|
|
|
2016-11-21 15:02:58 +00:00
|
|
|
if (_setupError) {
|
|
|
|
callback(_setupError, nil);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
dispatch_async(_jsQueue, ^{
|
2015-03-19 16:55:40 +00:00
|
|
|
if (!self.valid) {
|
2015-04-07 14:36:26 +00:00
|
|
|
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
|
2016-01-20 00:36:14 +00:00
|
|
|
NSLocalizedDescriptionKey: @"Runtime is not ready for debugging. Make sure Packager server is running."
|
2015-04-07 14:36:26 +00:00
|
|
|
}];
|
2015-03-19 16:55:40 +00:00
|
|
|
callback(error, nil);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
NSNumber *expectedID = @(lastID++);
|
2016-07-07 19:36:56 +00:00
|
|
|
self->_callbacks[expectedID] = [callback copy];
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSString *, id> *messageWithID = [message mutableCopy];
|
2015-04-11 22:08:00 +00:00
|
|
|
messageWithID[@"id"] = expectedID;
|
2016-07-07 19:36:56 +00:00
|
|
|
[self->_socket send:RCTJSONStringify(messageWithID, NULL)];
|
2015-04-11 22:08:00 +00:00
|
|
|
});
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
2015-10-16 15:10:25 +00:00
|
|
|
- (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, id> *message = @{
|
2015-07-28 22:48:46 +00:00
|
|
|
@"method": @"executeApplicationScript",
|
2015-08-24 10:14:33 +00:00
|
|
|
@"url": RCTNullIfNil(URL.absoluteString),
|
2015-07-28 22:48:46 +00:00
|
|
|
@"inject": _injectedObjects,
|
|
|
|
};
|
2016-11-21 15:02:58 +00:00
|
|
|
[self sendMessage:message onReply:^(NSError *error, NSDictionary<NSString *, id> *reply) {
|
2015-03-19 16:55:40 +00:00
|
|
|
onComplete(error);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-12-08 23:57:34 +00:00
|
|
|
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
|
|
|
[self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)callFunctionOnModule:(NSString *)module
|
|
|
|
method:(NSString *)method
|
|
|
|
arguments:(NSArray *)args
|
|
|
|
callback:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
|
|
|
[self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)invokeCallbackID:(NSNumber *)cbID
|
|
|
|
arguments:(NSArray *)args
|
|
|
|
callback:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
|
|
|
[self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
2015-03-19 16:55:40 +00:00
|
|
|
{
|
|
|
|
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, id> *message = @{
|
2015-12-08 23:57:34 +00:00
|
|
|
@"method": method,
|
2015-04-11 22:08:00 +00:00
|
|
|
@"arguments": arguments
|
|
|
|
};
|
2016-11-21 15:02:58 +00:00
|
|
|
[self sendMessage:message onReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
2015-03-19 16:55:40 +00:00
|
|
|
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
|
|
|
|
{
|
2015-04-11 22:08:00 +00:00
|
|
|
dispatch_async(_jsQueue, ^{
|
2016-07-07 19:36:56 +00:00
|
|
|
self->_injectedObjects[objectName] = script;
|
2015-03-19 16:55:40 +00:00
|
|
|
onComplete(nil);
|
2015-04-11 22:08:00 +00:00
|
|
|
});
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
2015-04-22 14:03:55 +00:00
|
|
|
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
2015-06-13 00:01:27 +00:00
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTExecuteOnMainQueue(block);
|
2015-06-13 00:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
2015-04-22 14:03:55 +00:00
|
|
|
{
|
|
|
|
dispatch_async(dispatch_get_main_queue(), block);
|
|
|
|
}
|
|
|
|
|
2015-03-19 16:55:40 +00:00
|
|
|
- (void)invalidate
|
|
|
|
{
|
|
|
|
_socket.delegate = nil;
|
|
|
|
[_socket closeWithCode:1000 reason:@"Invalidated"];
|
|
|
|
_socket = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isValid
|
|
|
|
{
|
2015-05-14 16:28:09 +00:00
|
|
|
return _socket != nil && _socket.readyState == RCTSR_OPEN;
|
2015-03-19 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2015-04-21 16:48:29 +00:00
|
|
|
|
|
|
|
#endif
|