2015-03-23 13:28:42 -07:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
*
|
2018-02-16 18:24:55 -08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2015-03-23 13:28:42 -07:00
|
|
|
*/
|
2015-03-19 09:55:40 -07:00
|
|
|
|
|
|
|
#import "RCTWebSocketExecutor.h"
|
|
|
|
|
2016-11-23 07:47:52 -08: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 09:28:09 -07:00
|
|
|
#import "RCTSRWebSocket.h"
|
2015-03-19 09:55:40 -07:00
|
|
|
|
2016-11-21 07:02:58 -08:00
|
|
|
#if RCT_DEV // Debug executors are only supported in dev mode
|
|
|
|
|
2015-11-14 10:25:00 -08:00
|
|
|
typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary<NSString *, id> *reply);
|
2015-05-14 09:28:09 -07:00
|
|
|
|
|
|
|
@interface RCTWebSocketExecutor () <RCTSRWebSocketDelegate>
|
2015-03-19 09:55:40 -07:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-04-11 15:08:00 -07:00
|
|
|
@implementation RCTWebSocketExecutor
|
|
|
|
{
|
2015-05-14 09:28:09 -07:00
|
|
|
RCTSRWebSocket *_socket;
|
2015-04-11 15:08:00 -07:00
|
|
|
dispatch_queue_t _jsQueue;
|
2015-11-14 10:25:00 -08:00
|
|
|
NSMutableDictionary<NSNumber *, RCTWSMessageCallback> *_callbacks;
|
2015-03-19 09:55:40 -07:00
|
|
|
dispatch_semaphore_t _socketOpenSemaphore;
|
2015-11-14 10:25:00 -08:00
|
|
|
NSMutableDictionary<NSString *, NSString *> *_injectedObjects;
|
2015-06-09 15:42:10 -07:00
|
|
|
NSURL *_url;
|
2016-11-21 07:02:58 -08:00
|
|
|
NSError *_setupError;
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
2015-06-09 15:42:10 -07:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2016-06-13 13:15:30 -07:00
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
|
2015-04-11 15:08:00 -07:00
|
|
|
- (instancetype)initWithURL:(NSURL *)URL
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
2015-06-15 07:53:45 -07:00
|
|
|
RCTAssertParam(URL);
|
|
|
|
|
2015-11-25 03:09:00 -08:00
|
|
|
if ((self = [self init])) {
|
2015-06-09 15:42:10 -07:00
|
|
|
_url = URL;
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-06-09 15:42:10 -07:00
|
|
|
- (void)setUp
|
|
|
|
{
|
2015-11-25 03:09:00 -08:00
|
|
|
if (!_url) {
|
2018-01-04 20:02:37 -08:00
|
|
|
NSInteger port = [[[_bridge bundleURL] port] integerValue] ?: RCT_METRO_PORT;
|
2017-03-22 05:27:58 -07:00
|
|
|
NSString *host = [[_bridge bundleURL] host] ?: @"localhost";
|
2017-09-25 10:23:02 -07:00
|
|
|
NSString *URLString = [NSString stringWithFormat:@"http://%@:%lld/debugger-proxy?role=client", host, (long long)port];
|
2015-11-25 03:09:00 -08:00
|
|
|
_url = [RCTConvert NSURL:URLString];
|
|
|
|
}
|
|
|
|
|
2016-04-01 07:01:51 -07:00
|
|
|
_jsQueue = dispatch_queue_create("com.facebook.react.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
|
2015-06-09 15:42:10 -07:00
|
|
|
_socket = [[RCTSRWebSocket alloc] initWithURL:_url];
|
|
|
|
_socket.delegate = self;
|
2015-11-14 10:25:00 -08:00
|
|
|
_callbacks = [NSMutableDictionary new];
|
2015-08-17 07:35:34 -07:00
|
|
|
_injectedObjects = [NSMutableDictionary new];
|
2015-06-09 15:42:10 -07:00
|
|
|
[_socket setDelegateDispatchQueue:_jsQueue];
|
|
|
|
|
2016-04-07 13:14:39 -07:00
|
|
|
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-js-devtools" relativeToURL:_url];
|
2015-06-09 15:42:10 -07:00
|
|
|
|
2016-09-27 06:19:45 -07:00
|
|
|
NSURLSession *session = [NSURLSession sharedSession];
|
|
|
|
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:startDevToolsURL]
|
|
|
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){}];
|
|
|
|
[dataTask resume];
|
2015-06-09 15:42:10 -07:00
|
|
|
if (![self connectToProxy]) {
|
|
|
|
[self invalidate];
|
2016-11-21 07:02:58 -08: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 15:42:10 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSInteger retries = 3;
|
|
|
|
BOOL runtimeIsReady = [self prepareJSRuntime];
|
|
|
|
while (!runtimeIsReady && retries > 0) {
|
|
|
|
runtimeIsReady = [self prepareJSRuntime];
|
|
|
|
retries--;
|
|
|
|
}
|
|
|
|
if (!runtimeIsReady) {
|
|
|
|
[self invalidate];
|
2016-11-21 07:02:58 -08: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 15:42:10 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-19 09:55:40 -07:00
|
|
|
- (BOOL)connectToProxy
|
|
|
|
{
|
|
|
|
_socketOpenSemaphore = dispatch_semaphore_create(0);
|
|
|
|
[_socket open];
|
2016-04-19 17:12:24 -07:00
|
|
|
long connected = dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10));
|
2016-11-21 07:02:58 -08:00
|
|
|
return connected == 0 && _socket.readyState == RCTSR_OPEN;
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)prepareJSRuntime
|
|
|
|
{
|
|
|
|
__block NSError *initError;
|
|
|
|
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
2016-11-21 07:02:58 -08:00
|
|
|
[self sendMessage:@{@"method": @"prepareJSRuntime"} onReply:^(NSError *error, NSDictionary<NSString *, id> *reply) {
|
2015-03-19 09:55:40 -07:00
|
|
|
initError = error;
|
|
|
|
dispatch_semaphore_signal(s);
|
|
|
|
}];
|
2017-11-21 13:57:47 -08:00
|
|
|
long runtimeIsReady = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5));
|
2016-11-21 07:02:58 -08:00
|
|
|
if (initError) {
|
|
|
|
RCTLogInfo(@"Websocket runtime setup failed: %@", initError);
|
|
|
|
}
|
2015-03-19 09:55:40 -07:00
|
|
|
return runtimeIsReady == 0 && initError == nil;
|
|
|
|
}
|
|
|
|
|
2015-05-14 09:28:09 -07:00
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
|
|
|
NSError *error = nil;
|
2015-11-14 10:25:00 -08:00
|
|
|
NSDictionary<NSString *, id> *reply = RCTJSONParse(message, &error);
|
2015-08-21 10:15:04 -07:00
|
|
|
NSNumber *messageID = reply[@"replyID"];
|
|
|
|
RCTWSMessageCallback callback = _callbacks[messageID];
|
|
|
|
if (callback) {
|
|
|
|
callback(error, reply);
|
2016-12-07 17:11:57 -08:00
|
|
|
[_callbacks removeObjectForKey:messageID];
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-14 09:28:09 -07:00
|
|
|
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
|
|
|
dispatch_semaphore_signal(_socketOpenSemaphore);
|
|
|
|
}
|
|
|
|
|
2015-05-14 09:28:09 -07:00
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
2015-12-23 13:31:44 -08:00
|
|
|
dispatch_semaphore_signal(_socketOpenSemaphore);
|
2016-11-21 07:02:58 -08:00
|
|
|
RCTLogInfo(@"WebSocket connection failed with error %@", error);
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
2016-11-21 07:02:58 -08:00
|
|
|
- (void)sendMessage:(NSDictionary<NSString *, id> *)message onReply:(RCTWSMessageCallback)callback
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
|
|
|
static NSUInteger lastID = 10000;
|
|
|
|
|
2016-11-21 07:02:58 -08:00
|
|
|
if (_setupError) {
|
|
|
|
callback(_setupError, nil);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-11 15:08:00 -07:00
|
|
|
dispatch_async(_jsQueue, ^{
|
2015-03-19 09:55:40 -07:00
|
|
|
if (!self.valid) {
|
2017-06-12 03:31:44 -07:00
|
|
|
callback(RCTErrorWithMessage(@"Runtime is not ready for debugging. Make sure Packager server is running."), nil);
|
2015-03-19 09:55:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-11 15:08:00 -07:00
|
|
|
NSNumber *expectedID = @(lastID++);
|
2016-07-07 12:36:56 -07:00
|
|
|
self->_callbacks[expectedID] = [callback copy];
|
2015-11-14 10:25:00 -08:00
|
|
|
NSMutableDictionary<NSString *, id> *messageWithID = [message mutableCopy];
|
2015-04-11 15:08:00 -07:00
|
|
|
messageWithID[@"id"] = expectedID;
|
2016-07-07 12:36:56 -07:00
|
|
|
[self->_socket send:RCTJSONStringify(messageWithID, NULL)];
|
2015-04-11 15:08:00 -07:00
|
|
|
});
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
2015-10-16 08:10:25 -07:00
|
|
|
- (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
2015-03-19 09:55:40 -07:00
|
|
|
{
|
2017-06-22 08:17:03 -07:00
|
|
|
// Hack: the bridge transitions out of loading state as soon as this method returns, which prevents us
|
|
|
|
// from completely invalidating the bridge and preventing an endless barage of RCTLog.logIfNoNativeHook
|
|
|
|
// calls if the JS execution environment is broken. We therefore block this thread until this message has returned.
|
|
|
|
dispatch_semaphore_t scriptSem = dispatch_semaphore_create(0);
|
|
|
|
|
2015-11-14 10:25:00 -08:00
|
|
|
NSDictionary<NSString *, id> *message = @{
|
2015-07-28 15:48:46 -07:00
|
|
|
@"method": @"executeApplicationScript",
|
2015-08-24 09:14:33 -01:00
|
|
|
@"url": RCTNullIfNil(URL.absoluteString),
|
2015-07-28 15:48:46 -07:00
|
|
|
@"inject": _injectedObjects,
|
|
|
|
};
|
2017-06-12 03:31:44 -07:00
|
|
|
[self sendMessage:message onReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
|
|
|
if (socketError) {
|
|
|
|
onComplete(socketError);
|
|
|
|
} else {
|
|
|
|
NSString *error = reply[@"error"];
|
|
|
|
onComplete(error ? RCTErrorWithMessage(error) : nil);
|
|
|
|
}
|
2017-06-22 08:17:03 -07:00
|
|
|
dispatch_semaphore_signal(scriptSem);
|
2015-03-19 09:55:40 -07:00
|
|
|
}];
|
2017-06-22 08:17:03 -07:00
|
|
|
|
|
|
|
dispatch_semaphore_wait(scriptSem, DISPATCH_TIME_FOREVER);
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
2015-12-08 15:57:34 -08: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 09:55:40 -07:00
|
|
|
{
|
|
|
|
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
2015-11-14 10:25:00 -08:00
|
|
|
NSDictionary<NSString *, id> *message = @{
|
2015-12-08 15:57:34 -08:00
|
|
|
@"method": method,
|
2015-04-11 15:08:00 -07:00
|
|
|
@"arguments": arguments
|
|
|
|
};
|
2016-11-21 07:02:58 -08:00
|
|
|
[self sendMessage:message onReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
2015-03-19 09:55:40 -07:00
|
|
|
if (socketError) {
|
|
|
|
onComplete(nil, socketError);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-12 03:31:44 -07:00
|
|
|
NSError *jsonError;
|
|
|
|
id result = RCTJSONParse(reply[@"result"], &jsonError);
|
|
|
|
NSString *error = reply[@"error"];
|
|
|
|
onComplete(result, error ? RCTErrorWithMessage(error) : jsonError);
|
2015-03-19 09:55:40 -07:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete
|
|
|
|
{
|
2015-04-11 15:08:00 -07:00
|
|
|
dispatch_async(_jsQueue, ^{
|
2016-07-07 12:36:56 -07:00
|
|
|
self->_injectedObjects[objectName] = script;
|
2015-03-19 09:55:40 -07:00
|
|
|
onComplete(nil);
|
2015-04-11 15:08:00 -07:00
|
|
|
});
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
2015-04-22 07:03:55 -07:00
|
|
|
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
2015-06-12 17:01:27 -07:00
|
|
|
{
|
2016-06-06 07:57:55 -07:00
|
|
|
RCTExecuteOnMainQueue(block);
|
2015-06-12 17:01:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
2015-04-22 07:03:55 -07:00
|
|
|
{
|
|
|
|
dispatch_async(dispatch_get_main_queue(), block);
|
|
|
|
}
|
|
|
|
|
2015-03-19 09:55:40 -07:00
|
|
|
- (void)invalidate
|
|
|
|
{
|
|
|
|
_socket.delegate = nil;
|
|
|
|
[_socket closeWithCode:1000 reason:@"Invalidated"];
|
|
|
|
_socket = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isValid
|
|
|
|
{
|
2015-05-14 09:28:09 -07:00
|
|
|
return _socket != nil && _socket.readyState == RCTSR_OPEN;
|
2015-03-19 09:55:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2015-04-21 09:48:29 -07:00
|
|
|
|
|
|
|
#endif
|