diff --git a/BUCK b/BUCK index cdbf57b39941cb..a26e8b2852641d 100644 --- a/BUCK +++ b/BUCK @@ -305,7 +305,6 @@ REACT_PUBLIC_HEADERS = { "React/RCTRootShadowView.h": RCTVIEWS_PATH + "RCTRootShadowView.h", "React/RCTRootView.h": RCTBASE_PATH + "RCTRootView.h", "React/RCTRootViewDelegate.h": RCTBASE_PATH + "RCTRootViewDelegate.h", - "React/RCTSRWebSocket.h": RCTLIB_PATH + "WebSocket/RCTSRWebSocket.h", "React/RCTScrollEvent.h": RCTVIEWS_PATH + "ScrollView/RCTScrollEvent.h", "React/RCTScrollView.h": RCTVIEWS_PATH + "ScrollView/RCTScrollView.h", "React/RCTScrollableProtocol.h": RCTVIEWS_PATH + "ScrollView/RCTScrollableProtocol.h", @@ -445,6 +444,7 @@ rn_xplat_cxx_library2( ], deps = [ YOGA_CXX_TARGET, + "//fbobjc/VendorLib/SocketRocket:SocketRocket", react_native_xplat_target("cxxreact:bridge"), react_native_xplat_target("reactperflogger:reactperflogger"), ], diff --git a/Libraries/WebSocket/RCTReconnectingWebSocket.m b/Libraries/WebSocket/RCTReconnectingWebSocket.m index bbd8c2273aceeb..0c477bb2d2697b 100644 --- a/Libraries/WebSocket/RCTReconnectingWebSocket.m +++ b/Libraries/WebSocket/RCTReconnectingWebSocket.m @@ -10,16 +10,16 @@ #import #import -#import +#import #if RCT_DEV // Only supported in dev mode -@interface RCTReconnectingWebSocket () +@interface RCTReconnectingWebSocket () @end @implementation RCTReconnectingWebSocket { NSURL *_url; - RCTSRWebSocket *_socket; + SRWebSocket *_socket; BOOL _stopped; } @@ -39,14 +39,14 @@ - (instancetype)initWithURL:(NSURL *)url - (void)send:(id)data { - [_socket send:data]; + [_socket sendData:data error:nil]; } - (void)start { [self stop]; _stopped = NO; - _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; + _socket = [[SRWebSocket alloc] initWithURL:_url]; _socket.delegate = self; [_socket setDelegateDispatchQueue:_delegateDispatchQueue]; [_socket open]; @@ -60,7 +60,7 @@ - (void)stop _socket = nil; } -- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { [_delegate reconnectingWebSocket:self didReceiveMessage:message]; } @@ -71,7 +71,7 @@ - (void)reconnect return; } - __weak RCTSRWebSocket *socket = _socket; + __weak SRWebSocket *socket = _socket; __weak __typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -82,12 +82,12 @@ - (void)reconnect }); } -- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket +- (void)webSocketDidOpen:(SRWebSocket *)webSocket { [_delegate reconnectingWebSocketDidOpen:self]; } -- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { [_delegate reconnectingWebSocketDidClose:self]; if ([error code] != ECONNREFUSED) { @@ -95,7 +95,7 @@ - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error } } -- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { [_delegate reconnectingWebSocketDidClose:self]; [self reconnect]; diff --git a/Libraries/WebSocket/RCTSRWebSocket.h b/Libraries/WebSocket/RCTSRWebSocket.h deleted file mode 100644 index 1b17cffaf47c08..00000000000000 --- a/Libraries/WebSocket/RCTSRWebSocket.h +++ /dev/null @@ -1,132 +0,0 @@ -// -// 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 -#import - -typedef NS_ENUM(unsigned int, RCTSRReadyState) { - RCTSR_CONNECTING = 0, - RCTSR_OPEN = 1, - RCTSR_CLOSING = 2, - RCTSR_CLOSED = 3, -}; - -typedef NS_ENUM(NSInteger, RCTSRStatusCode) { - RCTSRStatusCodeNormal = 1000, - RCTSRStatusCodeGoingAway = 1001, - RCTSRStatusCodeProtocolError = 1002, - RCTSRStatusCodeUnhandledType = 1003, - // 1004 reserved. - RCTSRStatusNoStatusReceived = 1005, - // 1004-1006 reserved. - RCTSRStatusCodeInvalidUTF8 = 1007, - RCTSRStatusCodePolicyViolated = 1008, - RCTSRStatusCodeMessageTooBig = 1009, -}; - -@class RCTSRWebSocket; - -extern NSString *const RCTSRWebSocketErrorDomain; -extern NSString *const RCTSRHTTPResponseErrorKey; - -#pragma mark - RCTSRWebSocketDelegate - -@protocol RCTSRWebSocketDelegate; - -#pragma mark - RCTSRWebSocket - -@interface RCTSRWebSocket : NSObject - -@property (nonatomic, weak) id delegate; - -@property (nonatomic, readonly) RCTSRReadyState readyState; -@property (nonatomic, readonly, strong) 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. -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithURLRequest:(NSURLRequest *)request; - -// Some helper constructors. -- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; -- (instancetype)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 RCTSR_networkRunLoop] using defaultModes. -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - -// RCTSRWebSockets 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 - RCTSRWebSocketDelegate - -@protocol RCTSRWebSocketDelegate - -// message will either be an NSString if the server is using text -// or NSData if the server is using binary. -- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message; - -@optional - -- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket; -- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error; -- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; -- (void)webSocket:(RCTSRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; - -@end - -#pragma mark - NSURLRequest (CertificateAdditions) - -@interface NSURLRequest (CertificateAdditions) - -@property (nonatomic, readonly, copy) NSArray *RCTSR_SSLPinnedCertificates; - -@end - -#pragma mark - NSMutableURLRequest (CertificateAdditions) - -@interface NSMutableURLRequest (CertificateAdditions) - -@property (nonatomic, copy) NSArray *RCTSR_SSLPinnedCertificates; - -@end - -#pragma mark - NSRunLoop (RCTSRWebSocket) - -@interface NSRunLoop (RCTSRWebSocket) - -+ (NSRunLoop *)RCTSR_networkRunLoop; - -@end diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m deleted file mode 100644 index 2356e50cfd1a05..00000000000000 --- a/Libraries/WebSocket/RCTSRWebSocket.m +++ /dev/null @@ -1,1681 +0,0 @@ -// -// 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 // [macOS] -#if !TARGET_OS_OSX // [macOS] -#import -#endif // [macOS] -#import - -#import - -#import - -#import -#import -#import - -typedef NS_ENUM(NSInteger, RCTSROpCode) { - RCTSROpCodeTextFrame = 0x1, - RCTSROpCodeBinaryFrame = 0x2, - // 3-7 reserved. - RCTSROpCodeConnectionClose = 0x8, - RCTSROpCodePing = 0x9, - RCTSROpCodePong = 0xA, - // B-F reserved. -}; - -typedef struct { - BOOL fin; - // BOOL rsv1; - // BOOL rsv2; - // BOOL rsv3; - uint8_t opcode; - BOOL masked; - uint64_t payload_length; -} frame_header; - -static NSString *const RCTSRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - -//#define RCTSR_ENABLE_LOG -#ifdef RCTSR_ENABLE_LOG -#define RCTSRLog(format...) RCTLogInfo(format) -#else -#define RCTSRLog(...) do { } while (0) -#endif - -// This is a hack, and probably not optimal -static inline int32_t validate_dispatch_data_partial_string(NSData *data) -{ - static const int maxCodepointSize = 3; - - for (int i = 0; i < maxCodepointSize; i++) { - NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; - if (str) { - return (int32_t)data.length - i; - } - } - - return -1; -} - -@interface NSData (RCTSRWebSocket) - -@property (nonatomic, readonly, copy) NSString *stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSString (RCTSRWebSocket) - -@property (nonatomic, readonly, copy) NSString *stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSURL (RCTSRWebSocket) - -// The origin isn't really applicable for a native application. -// So instead, just map ws -> http and wss -> https. -@property (nonatomic, readonly, copy) NSString *RCTSR_origin; - -@end - - -@interface _RCTSRRunLoopThread : NSThread - -@property (nonatomic, readonly) NSRunLoop *runLoop; - -@end - - -static NSString *newSHA1String(const char *bytes, size_t length) -{ - uint8_t md[CC_SHA1_DIGEST_LENGTH]; - - assert(length >= 0); - assert(length <= UINT32_MAX); - CC_SHA1(bytes, (CC_LONG)length, md); - - NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; - return [data base64EncodedStringWithOptions:0]; -} - -@implementation NSData (RCTSRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding -{ - return newSHA1String(self.bytes, self.length); -} - -@end - - -@implementation NSString (RCTSRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding -{ - return newSHA1String(self.UTF8String, self.length); -} - -@end - -NSString *const RCTSRWebSocketErrorDomain = @"RCTSRWebSocketErrorDomain"; -NSString *const RCTSRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; - -// Returns number of bytes consumed. Returning 0 means you didn't match. -// Sends bytes to callback handler; -typedef size_t (^stream_scanner)(NSData *collected_data); - -typedef void (^data_callback)(RCTSRWebSocket *webSocket, NSData *data); - -@interface RCTSRIOConsumer : NSObject - -@property (nonatomic, copy, readonly) stream_scanner consumer; -@property (nonatomic, copy, readonly) data_callback handler; -@property (nonatomic, assign) size_t bytesNeeded; -@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; -@property (nonatomic, assign, readonly) BOOL unmaskBytes; - -@end - -// This class is not thread-safe, and is expected to always be run on the same queue. -@interface RCTSRIOConsumerPool : NSObject - -- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize NS_DESIGNATED_INITIALIZER; - -- (RCTSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -- (void)returnConsumer:(RCTSRIOConsumer *)consumer; - -@end - -@interface RCTSRWebSocket () - -@property (nonatomic, assign) RCTSRReadyState readyState; - -@property (nonatomic, strong) NSOperationQueue *delegateOperationQueue; -@property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue; - -@end - -@implementation RCTSRWebSocket -{ - NSInteger _webSocketVersion; - - NSOperationQueue *_delegateOperationQueue; - dispatch_queue_t _delegateDispatchQueue; - - dispatch_queue_t _workQueue; - NSMutableArray *_consumers; - - NSInputStream *_inputStream; - NSOutputStream *_outputStream; - - NSMutableData *_readBuffer; - NSUInteger _readBufferOffset; - - NSMutableData *_outputBuffer; - NSUInteger _outputBufferOffset; - - uint8_t _currentFrameOpcode; - size_t _currentFrameCount; - size_t _readOpCount; - uint32_t _currentStringScanPosition; - NSMutableData *_currentFrameData; - - NSString *_closeReason; - - NSString *_secKey; - - BOOL _pinnedCertFound; - - uint8_t _currentReadMaskKey[4]; - size_t _currentReadMaskOffset; - - BOOL _consumerStopped; - - BOOL _closeWhenFinishedWriting; - BOOL _failed; - - BOOL _secure; - NSURLRequest *_urlRequest; - - CFHTTPMessageRef _receivedHTTPHeaders; - - BOOL _sentClose; - BOOL _didFail; - int _closeCode; - - BOOL _isPumping; - - BOOL _cleanupScheduled; - - NSMutableSet *_scheduledRunloops; - - // We use this to retain ourselves. - __strong RCTSRWebSocket *_selfRetain; - - NSArray *_requestedProtocols; - RCTSRIOConsumerPool *_consumerPool; -} - -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols -{ - RCTAssertParam(request); - - if ((self = [super init])) { - _url = request.URL; - _urlRequest = request; - - _requestedProtocols = [protocols copy]; - - [self _RCTSR_commonInit]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)init) - -- (instancetype)initWithURLRequest:(NSURLRequest *)request -{ - return [self initWithURLRequest:request protocols:nil]; -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - return [self initWithURL:URL protocols:nil]; -} - -- (instancetype)initWithURL:(NSURL *)URL protocols:(NSArray *)protocols -{ - NSMutableURLRequest *request; - if (URL) { - // Build a mutable request so we can fill the cookie header. - request = [NSMutableURLRequest requestWithURL:URL]; - - // We load cookies from sharedHTTPCookieStorage (shared with XHR and - // fetch). To get HTTPS-only cookies for wss URLs, replace wss with https - // in the URL. - NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:true]; - if ([components.scheme isEqualToString:@"wss"]) { - components.scheme = @"https"; - } - - // Load and set the cookie header. - NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:components.URL]; - [request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]]; - } - return [self initWithURLRequest:request protocols:protocols]; -} - -- (void)_RCTSR_commonInit -{ - NSString *scheme = _url.scheme.lowercaseString; - assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); - - if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { - _secure = YES; - } - - _readyState = RCTSR_CONNECTING; - _consumerStopped = YES; - _webSocketVersion = 13; - - _workQueue = dispatch_queue_create("com.facebook.react.SRWebSocket", DISPATCH_QUEUE_SERIAL); - - // Going to set a specific on the queue so we can validate we're on the work queue - dispatch_queue_set_specific(_workQueue, (__bridge void *)self, (__bridge void *)_workQueue, NULL); - - _delegateDispatchQueue = dispatch_get_main_queue(); - - _readBuffer = [NSMutableData new]; - _outputBuffer = [NSMutableData new]; - - _currentFrameData = [NSMutableData new]; - - _consumers = [NSMutableArray new]; - - _consumerPool = [RCTSRIOConsumerPool new]; - - _scheduledRunloops = [NSMutableSet new]; - - [self _initializeStreams]; - - // default handlers -} - -- (void)assertOnWorkQueue -{ - assert(dispatch_get_specific((__bridge void *)self) == (__bridge void *)_workQueue); -} - -- (void)dealloc -{ - _inputStream.delegate = nil; - _outputStream.delegate = nil; - - [_inputStream close]; - [_outputStream close]; - - if (_receivedHTTPHeaders) { - CFRelease(_receivedHTTPHeaders); - _receivedHTTPHeaders = NULL; - } -} - -#ifndef NDEBUG - -- (void)setReadyState:(RCTSRReadyState)aReadyState -{ - [self willChangeValueForKey:@"readyState"]; - assert(aReadyState > _readyState); - _readyState = aReadyState; - [self didChangeValueForKey:@"readyState"]; -} - -#endif - -- (void)open -{ - assert(_url); - RCTAssert(_readyState == RCTSR_CONNECTING, @"Cannot call -(void)open on RCTSRWebSocket more than once"); - - _selfRetain = self; - - [self _connect]; -} - -// Calls block on delegate queue -- (void)_performDelegateBlock:(dispatch_block_t)block -{ - if (_delegateOperationQueue) { - [_delegateOperationQueue addOperationWithBlock:block]; - } else { - assert(_delegateDispatchQueue); - dispatch_async(_delegateDispatchQueue, block); - } -} - -- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue -{ - _delegateDispatchQueue = queue; -} - -- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage -{ - NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); - - if (acceptHeader == nil) { - return NO; - } - - NSString *concattedString = [_secKey stringByAppendingString:RCTSRWebSocketAppendToSecKeyString]; - NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; - - return [acceptHeader isEqualToString:expectedAccept]; -} - -- (void)_HTTPHeadersDidFinish -{ - NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); - - if (responseCode >= 400) { - RCTSRLog(@"Request failed with response code %ld", responseCode); - [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], RCTSRHTTPResponseErrorKey:@(responseCode)}]]; - return; - } - - if (![self _checkHandshake:_receivedHTTPHeaders]) { - [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2133 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"]}]]; - return; - } - - NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); - if (negotiatedProtocol) { - // Make sure we requested the protocol - if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { - [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2133 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"]}]]; - return; - } - - _protocol = negotiatedProtocol; - } - - self.readyState = RCTSR_OPEN; - - if (!_didFail) { - [self _readFrameNew]; - } - - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { - [self.delegate webSocketDidOpen:self]; - }; - }]; -} - -- (void)_readHTTPHeader -{ - if (_receivedHTTPHeaders == NULL) { - _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); - } - - [self _readUntilHeaderCompleteWithCallback:^(RCTSRWebSocket *socket, NSData *data) { - CFHTTPMessageAppendBytes(self->_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); - - if (CFHTTPMessageIsHeaderComplete(self->_receivedHTTPHeaders)) { - RCTSRLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); - [socket _HTTPHeadersDidFinish]; - } else { - [socket _readHTTPHeader]; - } - }]; -} - -- (void)didConnect -{ - RCTSRLog(@"Connected"); - CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); - - // Set host first so it defaults - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); - - NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; - int result __unused = SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); - assert(result == 0); - _secKey = [keyBytes base64EncodedStringWithOptions:0]; - assert([_secKey length] == 24); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.RCTSR_origin); - - if (_requestedProtocols && _requestedProtocols.count > 0) { - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); - } - - [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); - }]; - - NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); - - CFRelease(request); - - [self _writeData:message]; - [self _readHTTPHeader]; -} - -- (void)_initializeStreams -{ - assert(_url.port.unsignedIntValue <= UINT32_MAX); - uint32_t port = _url.port.unsignedIntValue; - if (port == 0) { - if (!_secure) { - port = 80; - } else { - port = 443; - } - } - NSString *host = _url.host; - - CFReadStreamRef readStream = NULL; - CFWriteStreamRef writeStream = NULL; - - CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); - - // [macOS - CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings(); - if (CFDictionaryContainsKey(proxySettings, kCFStreamPropertySOCKSProxyHost)) { - CFReadStreamSetProperty(readStream, kCFStreamPropertySOCKSProxy, proxySettings); - CFWriteStreamSetProperty(writeStream, kCFStreamPropertySOCKSProxy, proxySettings); - } - // macOS] - - _outputStream = CFBridgingRelease(writeStream); - _inputStream = CFBridgingRelease(readStream); - - - if (_secure) { - NSMutableDictionary *SSLOptions = [NSMutableDictionary new]; - - [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; - - // If we're using pinned certs, don't validate the certificate chain - if (_urlRequest.RCTSR_SSLPinnedCertificates.count) { - [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - } - -#if DEBUG - [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - RCTLogInfo(@"SocketRocket: In debug mode. Allowing connection to any root cert"); -#endif - - [_outputStream setProperty:SSLOptions - forKey:(__bridge id)kCFStreamPropertySSLSettings]; - } - - _inputStream.delegate = self; - _outputStream.delegate = self; -} - -- (void)_connect -{ - if (!_scheduledRunloops.count) { - // [macOS `scheduleInRunLoop:forMode:` takes in a non-null run loop parameter so let's be safe and verify that - NSRunLoop *runLoop = [NSRunLoop RCTSR_networkRunLoop]; - if (runLoop != nil) { - [self scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode]; - } else { - RCTSRLog(@"Failed connecting to RCTSR_networkRunLoop"); - } - // macOS] - } - - // [macOS We've seen a rare ASan crash where _inputStream seems to be invalid. This is just a safety check. -#if DEBUG - [self _validateStream:_outputStream name:@"_outputStream"]; - [self _validateStream:_inputStream name:@"_inputStream"]; -#endif - // macOS] - - [_outputStream open]; - [_inputStream open]; -} - -// [macOS -#if DEBUG -- (void)_validateStream:(NSStream *)stream name:(NSString *)name { - NSStreamStatus status = stream.streamStatus; - if (status != NSStreamStatusNotOpen) { - RCTLogWarn(@"%@ was already opened, why are we opening it again? status=%@", name, @(status)); - } - - if (stream.delegate == nil) { - RCTLogError(@"%@'s delegate is nil, did we clean it up too early?", name); - } -} -#endif -// macOS] - -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode -{ - [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; - [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; - - [_scheduledRunloops addObject:@[aRunLoop, mode]]; -} - -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode -{ - [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; - [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; - - [_scheduledRunloops removeObject:@[aRunLoop, mode]]; -} - -- (void)close -{ - [self closeWithCode:RCTSRStatusCodeNormal reason:nil]; -} - -- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason -{ - assert(code); - dispatch_async(_workQueue, ^{ - if (self.readyState == RCTSR_CLOSING || self.readyState == RCTSR_CLOSED) { - return; - } - - BOOL wasConnecting = self.readyState == RCTSR_CONNECTING; - - self.readyState = RCTSR_CLOSING; - - RCTSRLog(@"Closing with code %ld reason %@", code, reason); - - if (wasConnecting) { - [self _disconnect]; - return; - } - - size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; - NSData *payload = mutablePayload; - - ((uint16_t *)mutablePayload.mutableBytes)[0] = NSSwapBigShortToHost(code); - - if (reason) { - NSRange remainingRange = {0}; - - NSUInteger usedLength = 0; - - BOOL success __unused = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; - - assert(success); - assert(remainingRange.length == 0); - - if (usedLength != maxMsgSize) { - payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; - } - } - - [self _sendFrameWithOpcode:RCTSROpCodeConnectionClose data:payload]; - }); -} - -- (void)_closeWithProtocolError:(NSString *)message -{ - // Need to shunt this on the _callbackQueue first to see if they received any messages - [self _performDelegateBlock:^{ - [self closeWithCode:RCTSRStatusCodeProtocolError reason:message]; - dispatch_async(self->_workQueue, ^{ - [self _disconnect]; - }); - }]; -} - -- (void)_failWithError:(NSError *)error -{ - dispatch_async(_workQueue, ^{ - if (self.readyState != RCTSR_CLOSED) { - self->_failed = YES; - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { - [self.delegate webSocket:self didFailWithError:error]; - } - }]; - - self.readyState = RCTSR_CLOSED; - - RCTSRLog(@"Failing with error %@", error.localizedDescription); - - [self _disconnect]; - [self _scheduleCleanup]; - } - }); -} - -- (void)_writeData:(NSData *)data -{ - [self assertOnWorkQueue]; - - if (_closeWhenFinishedWriting) { - return; - } - [_outputBuffer appendData:data]; - [self _pumpWriting]; -} - -- (void)send:(id)data -{ - RCTAssert(self.readyState != RCTSR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); - if (nil == data) { - return; - } - // TODO: maybe not copy this for performance - data = [data copy]; - dispatch_async(_workQueue, ^{ - if ([data isKindOfClass:[NSString class]]) { - [self _sendFrameWithOpcode:RCTSROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; - } else if ([data isKindOfClass:[NSData class]]) { - [self _sendFrameWithOpcode:RCTSROpCodeBinaryFrame data:data]; - } else { - assert(NO); - } - }); -} - -- (void)sendPing:(NSData *)data -{ - RCTAssert(self.readyState == RCTSR_OPEN, @"Invalid State: Cannot call send: until connection is open"); - // TODO: maybe not copy this for performance - data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty - dispatch_async(_workQueue, ^{ - [self _sendFrameWithOpcode:RCTSROpCodePing data:data]; - }); -} - -- (void)handlePing:(NSData *)pingData -{ - // Need to pingpong this off _callbackQueue first to make sure messages happen in order - [self _performDelegateBlock:^{ - dispatch_async(self->_workQueue, ^{ - [self _sendFrameWithOpcode:RCTSROpCodePong data:pingData]; - }); - }]; -} - -- (void)handlePong:(NSData *)pongData -{ - RCTSRLog(@"Received pong"); - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) { - [self.delegate webSocket:self didReceivePong:pongData]; - } - }]; -} - -- (void)_handleMessage:(id)message -{ - RCTSRLog(@"Received message"); - [self _performDelegateBlock:^{ - [self.delegate webSocket:self didReceiveMessage:message]; - }]; -} - -static inline BOOL closeCodeIsValid(int closeCode) -{ - if (closeCode < 1000) { - return NO; - } - - if (closeCode >= 1000 && closeCode <= 1011) { - if (closeCode == 1004 || - closeCode == 1005 || - closeCode == 1006) { - return NO; - } - return YES; - } - - if (closeCode >= 3000 && closeCode <= 3999) { - return YES; - } - - if (closeCode >= 4000 && closeCode <= 4999) { - return YES; - } - - return NO; -} - -// Note from RFC: -// -// If there is a body, the first two -// bytes of the body MUST be a 2-byte unsigned integer (in network byte -// order) representing a status code with value /code/ defined in -// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 -// encoded data with value /reason/, the interpretation of which is not -// defined by this specification. - -- (void)handleCloseWithData:(NSData *)data -{ - size_t dataSize = data.length; - __block uint16_t closeCode = 0; - - RCTSRLog(@"Received close frame"); - - if (dataSize == 1) { - // TODO: handle error - [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; - return; - } else if (dataSize >= 2) { - [data getBytes:&closeCode length:sizeof(closeCode)]; - _closeCode = NSSwapBigShortToHost(closeCode); - if (!closeCodeIsValid(_closeCode)) { - [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; - return; - } - if (dataSize > 2) { - _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; - if (!_closeReason) { - [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; - return; - } - } - } else { - _closeCode = RCTSRStatusNoStatusReceived; - } - - [self assertOnWorkQueue]; - - if (self.readyState == RCTSR_OPEN) { - [self closeWithCode:1000 reason:nil]; - } - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); -} - -- (void)_disconnect -{ - [self assertOnWorkQueue]; - RCTSRLog(@"Trying to disconnect"); - _closeWhenFinishedWriting = YES; - [self _pumpWriting]; -} - -- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode -{ - // copy frameData before handling, - // to avoid concurrent updates to the value at the pointer - frameData = [frameData copy]; - - // Check that the current data is valid UTF8 - - BOOL isControlFrame = (opcode == RCTSROpCodePing || opcode == RCTSROpCodePong || opcode == RCTSROpCodeConnectionClose); - if (!isControlFrame) { - [self _readFrameNew]; - } else { - dispatch_async(_workQueue, ^{ - [self _readFrameContinue]; - }); - } - - switch (opcode) { - case RCTSROpCodeTextFrame: { - NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; - if (str == nil && frameData) { - [self closeWithCode:RCTSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); - - return; - } - [self _handleMessage:str]; - break; - } - case RCTSROpCodeBinaryFrame: - [self _handleMessage:[frameData copy]]; - break; - case RCTSROpCodeConnectionClose: - [self handleCloseWithData:frameData]; - break; - case RCTSROpCodePing: - [self handlePing:frameData]; - break; - case RCTSROpCodePong: - [self handlePong:frameData]; - break; - default: - [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; - // TODO: Handle invalid opcode - break; - } -} - -- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData -{ - assert(frame_header.opcode != 0); - - if (self.readyState != RCTSR_OPEN) { - return; - } - - BOOL isControlFrame = (frame_header.opcode == RCTSROpCodePing || frame_header.opcode == RCTSROpCodePong || frame_header.opcode == RCTSROpCodeConnectionClose); - - if (isControlFrame && !frame_header.fin) { - [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; - return; - } - - if (isControlFrame && frame_header.payload_length >= 126) { - [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; - return; - } - - if (!isControlFrame) { - _currentFrameOpcode = frame_header.opcode; - _currentFrameCount += 1; - } - - if (frame_header.payload_length == 0) { - if (isControlFrame) { - [self _handleFrameWithData:curData opCode:frame_header.opcode]; - } else { - if (frame_header.fin) { - [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; - } else { - // TODO: add assert that opcode is not a control; - [self _readFrameContinue]; - } - } - } else { - assert(frame_header.payload_length <= SIZE_T_MAX); - [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(RCTSRWebSocket *socket, NSData *newData) { - if (isControlFrame) { - [socket _handleFrameWithData:newData opCode:frame_header.opcode]; - } else { - if (frame_header.fin) { - [socket _handleFrameWithData:socket->_currentFrameData opCode:frame_header.opcode]; - } else { - // TODO: add assert that opcode is not a control; - [socket _readFrameContinue]; - } - - } - } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; - } -} - -/* From RFC: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ - */ - -static const uint8_t RCTSRFinMask = 0x80; -static const uint8_t RCTSROpCodeMask = 0x0F; -static const uint8_t RCTSRRsvMask = 0x70; -static const uint8_t RCTSRMaskMask = 0x80; -static const uint8_t RCTSRPayloadLenMask = 0x7F; - -- (void)_readFrameContinue -{ - assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); - - [self _addConsumerWithDataLength:2 callback:^(RCTSRWebSocket *socket, NSData *data) { - __block frame_header header = {0}; - - const uint8_t *headerBuffer = data.bytes; - assert(data.length >= 2); - - if (headerBuffer[0] & RCTSRRsvMask) { - [socket _closeWithProtocolError:@"Server used RSV bits"]; - return; - } - - uint8_t receivedOpcode = (RCTSROpCodeMask &headerBuffer[0]); - - BOOL isControlFrame = (receivedOpcode == RCTSROpCodePing || receivedOpcode == RCTSROpCodePong || receivedOpcode == RCTSROpCodeConnectionClose); - - if (!isControlFrame && receivedOpcode != 0 && socket->_currentFrameCount > 0) { - [socket _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; - return; - } - - if (receivedOpcode == 0 && socket->_currentFrameCount == 0) { - [socket _closeWithProtocolError:@"cannot continue a message"]; - return; - } - - header.opcode = receivedOpcode == 0 ? socket->_currentFrameOpcode : receivedOpcode; - - header.fin = !!(RCTSRFinMask &headerBuffer[0]); - - - header.masked = !!(RCTSRMaskMask &headerBuffer[1]); - header.payload_length = RCTSRPayloadLenMask & headerBuffer[1]; - - headerBuffer = NULL; - - if (header.masked) { - [socket _closeWithProtocolError:@"Client must receive unmasked data"]; - } - - size_t extra_bytes_needed = header.masked ? sizeof(self->_currentReadMaskKey) : 0; - - if (header.payload_length == 126) { - extra_bytes_needed += sizeof(uint16_t); - } else if (header.payload_length == 127) { - extra_bytes_needed += sizeof(uint64_t); - } - - if (extra_bytes_needed == 0) { - [socket _handleFrameHeader:header curData:socket->_currentFrameData]; - } else { - [socket _addConsumerWithDataLength:extra_bytes_needed callback:^(RCTSRWebSocket *_socket, NSData *_data) { - size_t mapped_size __unused = _data.length; - const void *mapped_buffer = _data.bytes; - size_t offset = 0; - - if (header.payload_length == 126) { - assert(mapped_size >= sizeof(uint16_t)); - uint16_t newLen = NSSwapBigShortToHost(*(uint16_t *)(mapped_buffer)); - header.payload_length = newLen; - offset += sizeof(uint16_t); - } else if (header.payload_length == 127) { - assert(mapped_size >= sizeof(uint64_t)); - header.payload_length = NSSwapBigLongLongToHost(*(uint64_t *)(mapped_buffer)); - offset += sizeof(uint64_t); - } else { - assert(header.payload_length < 126 && header.payload_length >= 0); - } - - if (header.masked) { - assert(mapped_size >= sizeof(self->_currentReadMaskOffset) + offset); - memcpy(_socket->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(_socket->_currentReadMaskKey)); - } - - [_socket _handleFrameHeader:header curData:_socket->_currentFrameData]; - } readToCurrentFrame:NO unmaskBytes:NO]; - } - } readToCurrentFrame:NO unmaskBytes:NO]; -} - -- (void)_readFrameNew -{ - dispatch_async(_workQueue, ^{ - self->_currentFrameData.length = 0; - - self->_currentFrameOpcode = 0; - self->_currentFrameCount = 0; - self->_readOpCount = 0; - self->_currentStringScanPosition = 0; - - [self _readFrameContinue]; - }); -} - -- (void)_pumpWriting -{ - [self assertOnWorkQueue]; - - NSUInteger dataLength = _outputBuffer.length; - if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { - NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; - if (bytesWritten == -1) { - [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:2145 userInfo:@{NSLocalizedDescriptionKey: @"Error writing to stream"}]]; - return; - } - - _outputBufferOffset += bytesWritten; - - if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { - _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; - _outputBufferOffset = 0; - } - } - - if (_closeWhenFinishedWriting && - _outputBuffer.length - _outputBufferOffset == 0 && - (_inputStream.streamStatus != NSStreamStatusNotOpen && - _inputStream.streamStatus != NSStreamStatusClosed) && - !_sentClose) { - _sentClose = YES; - - [self _scheduleCleanup]; - - if (!_failed) { - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:self->_closeCode reason:self->_closeReason wasClean:YES]; - } - }]; - } - } -} - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback -{ - [self assertOnWorkQueue]; - [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; -} - -- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes -{ - [self assertOnWorkQueue]; - assert(dataLength); - - [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; - [self _pumpScanner]; -} - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength -{ - [self assertOnWorkQueue]; - [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; - [self _pumpScanner]; -} - -static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; - -- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler -{ - [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; -} - -- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler -{ - // TODO: optimize so this can continue from where we last searched - stream_scanner consumer = ^size_t(NSData *data) { - __block size_t found_size = 0; - __block size_t match_count = 0; - - size_t size = data.length; - const unsigned char *buffer = data.bytes; - for (size_t i = 0; i < size; i++ ) { - if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { - match_count += 1; - if (match_count == length) { - found_size = i + 1; - break; - } - } else { - match_count = 0; - } - } - return found_size; - }; - [self _addConsumerWithScanner:consumer callback:dataHandler]; -} - -// Returns true if did work -- (BOOL)_innerPumpScanner -{ - BOOL didWork = NO; - - if (self.readyState >= RCTSR_CLOSING) { - return didWork; - } - - if (!_consumers.count) { - return didWork; - } - - size_t curSize = _readBuffer.length - _readBufferOffset; - if (!curSize) { - return didWork; - } - - RCTSRIOConsumer *consumer = _consumers[0]; - - size_t bytesNeeded = consumer.bytesNeeded; - - size_t foundSize = 0; - if (consumer.consumer) { - NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; - foundSize = consumer.consumer(tempView); - } else { - assert(consumer.bytesNeeded); - if (curSize >= bytesNeeded) { - foundSize = bytesNeeded; - } else if (consumer.readToCurrentFrame) { - foundSize = curSize; - } - } - - NSData *slice = nil; - if (consumer.readToCurrentFrame || foundSize) { - NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); - slice = [_readBuffer subdataWithRange:sliceRange]; - - _readBufferOffset += foundSize; - - if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { - _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; - } - - if (consumer.unmaskBytes) { - NSMutableData *mutableSlice = [slice mutableCopy]; - - NSUInteger len = mutableSlice.length; - uint8_t *bytes = mutableSlice.mutableBytes; - - for (NSUInteger i = 0; i < len; i++) { - bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; - _currentReadMaskOffset += 1; - } - - slice = mutableSlice; - } - - if (consumer.readToCurrentFrame) { - [_currentFrameData appendData:slice]; - - _readOpCount += 1; - - if (_currentFrameOpcode == RCTSROpCodeTextFrame) { - // Validate UTF8 stuff. - size_t currentDataSize = _currentFrameData.length; - if (_currentFrameOpcode == RCTSROpCodeTextFrame && currentDataSize > 0) { - // TODO: Optimize this. Don't really have to copy all the data each time - - size_t scanSize = currentDataSize - _currentStringScanPosition; - - NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; - int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); - - if (valid_utf8_size == -1) { - [self closeWithCode:RCTSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; - }); - return didWork; - } else { - _currentStringScanPosition += valid_utf8_size; - } - } - } - - consumer.bytesNeeded -= foundSize; - - if (consumer.bytesNeeded == 0) { - [_consumers removeObjectAtIndex:0]; - consumer.handler(self, nil); - [_consumerPool returnConsumer:consumer]; - didWork = YES; - } - } else if (foundSize) { - [_consumers removeObjectAtIndex:0]; - consumer.handler(self, slice); - [_consumerPool returnConsumer:consumer]; - didWork = YES; - } - } - return didWork; -} - -- (void)_pumpScanner -{ - [self assertOnWorkQueue]; - - if (!_isPumping) { - _isPumping = YES; - } else { - return; - } - - while ([self _innerPumpScanner]) {} - - _isPumping = NO; -} - -//#define NOMASK - -static const size_t RCTSRFrameHeaderOverhead = 32; - -- (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(NSData *)data -{ - [self assertOnWorkQueue]; - - if (nil == data) { - return; - } - - size_t payloadLength = [data length]; - - NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + RCTSRFrameHeaderOverhead]; - if (!frame) { - [self closeWithCode:RCTSRStatusCodeMessageTooBig reason:@"Message too big"]; - return; - } - uint8_t *frame_buffer = (uint8_t *)frame.mutableBytes; - - // set fin - frame_buffer[0] = RCTSRFinMask | opcode; - - BOOL useMask = YES; -#ifdef NOMASK - useMask = NO; -#endif - - if (useMask) { - // set the mask and header - frame_buffer[1] |= RCTSRMaskMask; - } - - size_t frame_buffer_size = 2; - - const uint8_t *unmasked_payload = (uint8_t *)[data bytes]; - - if (payloadLength < 126) { - frame_buffer[1] |= payloadLength; - } else if (payloadLength <= UINT16_MAX) { - frame_buffer[1] |= 126; - *((uint16_t *)(frame_buffer + frame_buffer_size)) = NSSwapBigShortToHost((uint16_t)payloadLength); - frame_buffer_size += sizeof(uint16_t); - } else { - frame_buffer[1] |= 127; - *((uint64_t *)(frame_buffer + frame_buffer_size)) = NSSwapBigLongLongToHost((uint64_t)payloadLength); - frame_buffer_size += sizeof(uint64_t); - } - - if (!useMask) { - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i]; - frame_buffer_size += 1; - } - } else { - uint8_t *mask_key = frame_buffer + frame_buffer_size; - int result __unused = SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); - assert(result == 0); - frame_buffer_size += sizeof(uint32_t); - - // TODO: could probably optimize this with SIMD - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; - frame_buffer_size += 1; - } - } - - assert(frame_buffer_size <= [frame length]); - frame.length = frame_buffer_size; - - [self _writeData:frame]; -} - -- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode -{ - if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { - NSArray *sslCerts = _urlRequest.RCTSR_SSLPinnedCertificates; - if (sslCerts) { - SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; - if (secTrust) { - NSInteger numCerts = SecTrustGetCertificateCount(secTrust); - for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); - NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); - - for (id ref in sslCerts) { - SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; - NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); - - if ([trustedCertData isEqualToData:certData]) { - _pinnedCertFound = YES; - break; - } - } - } - } - - if (!_pinnedCertFound) { - dispatch_async(_workQueue, ^{ - [self _failWithError:[NSError errorWithDomain:RCTSRWebSocketErrorDomain code:23556 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Invalid server cert"]}]]; - }); - return; - } - } - } - - // _workQueue cannot be NULL - if (!_workQueue) { - return; - } - __weak typeof(self) weakSelf = self; - dispatch_async(_workQueue, ^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongSelf safeHandleEvent:eventCode stream:aStream]; - }); -} - -- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream -{ - switch (eventCode) { - case NSStreamEventOpenCompleted: { - RCTSRLog(@"NSStreamEventOpenCompleted %@", aStream); - if (self.readyState >= RCTSR_CLOSING) { - return; - } - assert(self->_readBuffer); - - if (self.readyState == RCTSR_CONNECTING && aStream == self->_inputStream) { - [self didConnect]; - } - [self _pumpWriting]; - [self _pumpScanner]; - break; - } - - case NSStreamEventErrorOccurred: { - RCTSRLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [aStream.streamError copy]); - // TODO: specify error better! - [self _failWithError:aStream.streamError]; - self->_readBufferOffset = 0; - self->_readBuffer.length = 0; - break; - - } - - case NSStreamEventEndEncountered: { - [self _pumpScanner]; - RCTSRLog(@"NSStreamEventEndEncountered %@", aStream); - if (aStream.streamError) { - [self _failWithError:aStream.streamError]; - } else { - dispatch_async(self->_workQueue, ^{ - if (self.readyState != RCTSR_CLOSED) { - self.readyState = RCTSR_CLOSED; - [self _scheduleCleanup]; - } - - if (!self->_sentClose && !self->_failed) { - self->_sentClose = YES; - // If we get closed in this state it's probably not clean because we should be sending this when we send messages - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:RCTSRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO]; - } - }]; - } - }); - } - - break; - } - - case NSStreamEventHasBytesAvailable: { - RCTSRLog(@"NSStreamEventHasBytesAvailable %@", aStream); - const int bufferSize = 2048; - uint8_t buffer[bufferSize]; - - while (self->_inputStream.hasBytesAvailable) { - NSInteger bytes_read = [self->_inputStream read:buffer maxLength:bufferSize]; - - if (bytes_read > 0) { - [self->_readBuffer appendBytes:buffer length:bytes_read]; - } else if (bytes_read < 0) { - [self _failWithError:self->_inputStream.streamError]; - } - - if (bytes_read != bufferSize) { - break; - } - }; - [self _pumpScanner]; - break; - } - - case NSStreamEventHasSpaceAvailable: { - RCTSRLog(@"NSStreamEventHasSpaceAvailable %@", aStream); - [self _pumpWriting]; - break; - } - - default: - RCTSRLog(@"(default) %@", aStream); - break; - } -} - -- (void)_scheduleCleanup -{ - if (_cleanupScheduled) { - return; - } - - _cleanupScheduled = YES; - - // Cleanup NSStream's delegate in the same RunLoop used by the streams themselves: - // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc - NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; - [[NSRunLoop RCTSR_networkRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; -} - -- (void)_cleanupSelfReference:(NSTimer *)timer -{ - // Remove the streams, right now, from the networkRunLoop - [_inputStream close]; - [_outputStream close]; - - // Unschedule from RunLoop - for (NSArray *runLoop in [_scheduledRunloops copy]) { - [self unscheduleFromRunLoop:runLoop[0] forMode:runLoop[1]]; - } - - // Nuke NSStream's delegate - _inputStream.delegate = nil; - _outputStream.delegate = nil; - - // Cleanup selfRetain in the same GCD queue as usual - dispatch_async(_workQueue, ^{ - self->_selfRetain = nil; - }); -} - -@end - -@implementation RCTSRIOConsumer - -- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes -{ - _consumer = [scanner copy]; - _handler = [handler copy]; - _bytesNeeded = bytesNeeded; - _readToCurrentFrame = readToCurrentFrame; - _unmaskBytes = unmaskBytes; - assert(_consumer || _bytesNeeded); -} - -@end - -@implementation RCTSRIOConsumerPool -{ - NSUInteger _poolSize; - NSMutableArray *_bufferedConsumers; -} - -- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize -{ - if ((self = [super init])) { - _poolSize = poolSize; - _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; - } - return self; -} - -- (instancetype)init -{ - return [self initWithBufferCapacity:8]; -} - -- (RCTSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes -{ - RCTSRIOConsumer *consumer = nil; - if (_bufferedConsumers.count) { - consumer = _bufferedConsumers.lastObject; - [_bufferedConsumers removeLastObject]; - } else { - consumer = [RCTSRIOConsumer new]; - } - - [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; - - return consumer; -} - -- (void)returnConsumer:(RCTSRIOConsumer *)consumer -{ - if (_bufferedConsumers.count < _poolSize) { - [_bufferedConsumers addObject:consumer]; - } -} - -@end - -@implementation NSURLRequest (CertificateAdditions) - -- (NSArray *)RCTSR_SSLPinnedCertificates -{ - return [NSURLProtocol propertyForKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; -} - -@end - -@implementation NSMutableURLRequest (CertificateAdditions) - -- (NSArray *)RCTSR_SSLPinnedCertificates -{ - return [NSURLProtocol propertyForKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; -} - -- (void)setRCTSR_SSLPinnedCertificates:(NSArray *)RCTSR_SSLPinnedCertificates -{ - [NSURLProtocol setProperty:RCTSR_SSLPinnedCertificates forKey:@"RCTSR_SSLPinnedCertificates" inRequest:self]; -} - -@end - -@implementation NSURL (RCTSRWebSocket) - -- (NSString *)RCTSR_origin -{ - NSString *scheme = self.scheme.lowercaseString; - - if ([scheme isEqualToString:@"wss"]) { - scheme = @"https"; - } else if ([scheme isEqualToString:@"ws"]) { - scheme = @"http"; - } - - int defaultPort = ([scheme isEqualToString:@"https"] ? 443 : - [scheme isEqualToString:@"http"] ? 80 : - -1); - int port = self.port.intValue; - if (port > 0 && port != defaultPort) { - return [NSString stringWithFormat:@"%@://%@:%d", scheme, self.host, port]; - } else { - return [NSString stringWithFormat:@"%@://%@", scheme, self.host]; - } -} - -@end - -static _RCTSRRunLoopThread *networkThread = nil; -static NSRunLoop *networkRunLoop = nil; - -@implementation NSRunLoop (RCTSRWebSocket) - -+ (NSRunLoop *)RCTSR_networkRunLoop -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - networkThread = [_RCTSRRunLoopThread new]; - networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; - [networkThread start]; - networkRunLoop = networkThread.runLoop; - }); - - return networkRunLoop; -} - -@end - -@implementation _RCTSRRunLoopThread -{ - dispatch_group_t _waitGroup; -} - -@synthesize runLoop = _runLoop; - -- (instancetype)init -{ - if ((self = [super init])) { - _waitGroup = dispatch_group_create(); - dispatch_group_enter(_waitGroup); - } - return self; -} - -- (void)main -{ - @autoreleasepool { - _runLoop = [NSRunLoop currentRunLoop]; - dispatch_group_leave(_waitGroup); - - NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:self selector:@selector(step) userInfo:nil repeats:NO]; - [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; - - while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { } - assert(NO); - } -} - -- (void)step -{ - // Does nothing -} - -- (NSRunLoop *)runLoop -{ - dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); - return _runLoop; -} - -@end diff --git a/React-Core.podspec b/React-Core.podspec index 3eabdeddc0c347..89e6d9587ec1dd 100644 --- a/React-Core.podspec +++ b/React-Core.podspec @@ -18,6 +18,7 @@ end folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' folly_version = '2021.06.28.00-v2' +socket_rocket_version = '0.6.0' boost_compiler_flags = '-Wno-documentation' header_subspecs = { @@ -104,6 +105,7 @@ Pod::Spec.new do |s| s.dependency "React-perflogger", version s.dependency "React-jsi", version s.dependency "React-jsiexecutor", version + s.dependency "SocketRocket", socket_rocket_version s.dependency "Yoga" s.dependency "glog" end diff --git a/React/CoreModules/RCTWebSocketExecutor.mm b/React/CoreModules/RCTWebSocketExecutor.mm index 9418dd63891e17..1b03cbcd37f86a 100644 --- a/React/CoreModules/RCTWebSocketExecutor.mm +++ b/React/CoreModules/RCTWebSocketExecutor.mm @@ -12,10 +12,10 @@ #import #import #import -#import #import #import +#import #import "CoreModulesPlugins.h" @@ -23,12 +23,12 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); -@interface RCTWebSocketExecutor () +@interface RCTWebSocketExecutor () @end @implementation RCTWebSocketExecutor { - RCTSRWebSocket *_socket; + SRWebSocket *_socket; dispatch_queue_t _jsQueue; NSMutableDictionary *_callbacks; dispatch_semaphore_t _socketOpenSemaphore; @@ -62,7 +62,7 @@ - (void)setUp } _jsQueue = dispatch_queue_create("com.facebook.react.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); - _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; + _socket = [[SRWebSocket alloc] initWithURL:_url]; _socket.delegate = self; _callbacks = [NSMutableDictionary new]; _injectedObjects = [NSMutableDictionary new]; @@ -112,7 +112,7 @@ - (BOOL)connectToProxy _socketOpenSemaphore = dispatch_semaphore_create(0); [_socket open]; long connected = dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 15)); - return connected == 0 && _socket.readyState == RCTSR_OPEN; + return connected == 0 && _socket.readyState == SR_OPEN; } - (BOOL)prepareJSRuntime @@ -131,7 +131,7 @@ - (BOOL)prepareJSRuntime return runtimeIsReady == 0 && initError == nil; } -- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { NSError *error = nil; NSDictionary *reply = RCTJSONParse(message, &error); @@ -143,12 +143,12 @@ - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message } } -- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket +- (void)webSocketDidOpen:(SRWebSocket *)webSocket { dispatch_semaphore_signal(_socketOpenSemaphore); } -- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { dispatch_semaphore_signal(_socketOpenSemaphore); RCTLogInfo(@"WebSocket connection failed with error %@", error); @@ -173,7 +173,7 @@ - (void)sendMessage:(NSDictionary *)message onReply:(RCTWSMessag self->_callbacks[expectedID] = [callback copy]; NSMutableDictionary *messageWithID = [message mutableCopy]; messageWithID[@"id"] = expectedID; - [self->_socket send:RCTJSONStringify(messageWithID, NULL)]; + [self->_socket sendString:RCTJSONStringify(messageWithID, NULL) error:nil]; }); } @@ -270,7 +270,7 @@ - (void)invalidate - (BOOL)isValid { - return _socket != nil && _socket.readyState == RCTSR_OPEN; + return _socket != nil && _socket.readyState == SR_OPEN; } - (void)dealloc diff --git a/React/CoreModules/RCTWebSocketModule.mm b/React/CoreModules/RCTWebSocketModule.mm index 509e56bb1437b1..c6faada53e37bc 100644 --- a/React/CoreModules/RCTWebSocketModule.mm +++ b/React/CoreModules/RCTWebSocketModule.mm @@ -11,12 +11,12 @@ #import #import -#import #import +#import #import "CoreModulesPlugins.h" -@implementation RCTSRWebSocket (React) +@implementation SRWebSocket (React) - (NSNumber *)reactTag { @@ -30,12 +30,12 @@ - (void)setReactTag:(NSNumber *)reactTag @end -@interface RCTWebSocketModule () +@interface RCTWebSocketModule () @end @implementation RCTWebSocketModule { - NSMutableDictionary *_sockets; + NSMutableDictionary *_sockets; NSMutableDictionary> *_contentHandlers; } @@ -56,7 +56,7 @@ - (void)invalidate [super invalidate]; _contentHandlers = nil; - for (RCTSRWebSocket *socket in _sockets.allValues) { + for (SRWebSocket *socket in _sockets.allValues) { socket.delegate = nil; [socket close]; } @@ -92,7 +92,7 @@ - (void)invalidate }]; } - RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:request protocols:protocols]; + SRWebSocket *webSocket = [[SRWebSocket alloc] initWithURLRequest:request protocols:protocols]; [webSocket setDelegateDispatchQueue:[self methodQueue]]; webSocket.delegate = self; webSocket.reactTag = @(socketID); @@ -106,7 +106,7 @@ - (void)invalidate RCT_EXPORT_METHOD(send : (NSString *)message forSocketID : (double)socketID) { - [_sockets[@(socketID)] send:message]; + [_sockets[@(socketID)] sendString:message error:nil]; } RCT_EXPORT_METHOD(sendBinary : (NSString *)base64String forSocketID : (double)socketID) @@ -116,12 +116,12 @@ - (void)invalidate - (void)sendData:(NSData *)data forSocketID:(NSNumber *__nonnull)socketID { - [_sockets[socketID] send:data]; + [_sockets[socketID] sendData:data error:nil]; } RCT_EXPORT_METHOD(ping : (double)socketID) { - [_sockets[@(socketID)] sendPing:NULL]; + [_sockets[@(socketID)] sendPing:nil error:nil]; } RCT_EXPORT_METHOD(close : (double)code reason : (NSString *)reason socketID : (double)socketID) @@ -140,7 +140,7 @@ - (void)setContentHandler:(id)handler forSocketID:(N #pragma mark - RCTSRWebSocketDelegate methods -- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { NSString *type; @@ -160,13 +160,13 @@ - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message [self sendEventWithName:@"websocketMessage" body:@{@"data" : message, @"type" : type, @"id" : webSocket.reactTag}]; } -- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket +- (void)webSocketDidOpen:(SRWebSocket *)webSocket { [self sendEventWithName:@"websocketOpen" body:@{@"id" : webSocket.reactTag, @"protocol" : webSocket.protocol ? webSocket.protocol : @""}]; } -- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { NSNumber *socketID = [webSocket reactTag]; _contentHandlers[socketID] = nil; @@ -176,7 +176,7 @@ - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error [self sendEventWithName:@"websocketFailed" body:body]; } -- (void)webSocket:(RCTSRWebSocket *)webSocket +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean diff --git a/React/CoreModules/React-CoreModules.podspec b/React/CoreModules/React-CoreModules.podspec index 3a5ef0e623c01f..3df068a61bff5a 100644 --- a/React/CoreModules/React-CoreModules.podspec +++ b/React/CoreModules/React-CoreModules.podspec @@ -18,6 +18,7 @@ end folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' folly_version = '2021.06.28.00-v2' +socket_rocket_version = '0.6.0' Pod::Spec.new do |s| s.name = "React-CoreModules" @@ -48,4 +49,5 @@ Pod::Spec.new do |s| s.dependency "React-RCTImage", version s.dependency "ReactCommon/turbomodule/core", version s.dependency "React-jsi", version + s.dependency "SocketRocket", socket_rocket_version end diff --git a/React/DevSupport/RCTPackagerConnection.mm b/React/DevSupport/RCTPackagerConnection.mm index 0685a4e2a94c80..bb91b9820ed7e6 100644 --- a/React/DevSupport/RCTPackagerConnection.mm +++ b/React/DevSupport/RCTPackagerConnection.mm @@ -23,7 +23,7 @@ #if RCT_DEV -#import +#import @interface RCTPackagerConnection () @end diff --git a/React/Inspector/RCTInspector.mm b/React/Inspector/RCTInspector.mm index ff1c007dd09810..15e320fc1cb3a3 100644 --- a/React/Inspector/RCTInspector.mm +++ b/React/Inspector/RCTInspector.mm @@ -14,7 +14,6 @@ #import #import #import -#import #import using namespace facebook::react; diff --git a/React/Inspector/RCTInspectorPackagerConnection.m b/React/Inspector/RCTInspectorPackagerConnection.m index 532c2d9c78bb92..c76d43e352d3c7 100644 --- a/React/Inspector/RCTInspectorPackagerConnection.m +++ b/React/Inspector/RCTInspectorPackagerConnection.m @@ -12,8 +12,8 @@ #import #import #import -#import #import +#import // This is a port of the Android impl, at // ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java @@ -24,10 +24,10 @@ @implementation RCTBundleStatus @end -@interface RCTInspectorPackagerConnection () { +@interface RCTInspectorPackagerConnection () { NSURL *_url; NSMutableDictionary *_inspectorConnections; - RCTSRWebSocket *_webSocket; + SRWebSocket *_webSocket; dispatch_queue_t _jsQueue; BOOL _closed; BOOL _suppressConnectionErrors; @@ -188,7 +188,7 @@ - (void)sendEvent:(NSString *)name payload:(id)payload } // analogous to InspectorPackagerConnection.Connection.onFailure(...) -- (void)webSocket:(__unused RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +- (void)webSocket:(__unused SRWebSocket *)webSocket didFailWithError:(NSError *)error { if (_webSocket) { [self abort:@"Websocket exception" withCause:error]; @@ -199,7 +199,7 @@ - (void)webSocket:(__unused RCTSRWebSocket *)webSocket didFailWithError:(NSError } // analogous to InspectorPackagerConnection.Connection.onMessage(...) -- (void)webSocket:(__unused RCTSRWebSocket *)webSocket didReceiveMessage:(id)opaqueMessage +- (void)webSocket:(__unused SRWebSocket *)webSocket didReceiveMessage:(id)opaqueMessage { // warn but don't die on unrecognized messages if (![opaqueMessage isKindOfClass:[NSString class]]) { @@ -219,7 +219,7 @@ - (void)webSocket:(__unused RCTSRWebSocket *)webSocket didReceiveMessage:(id)opa } // analogous to InspectorPackagerConnection.Connection.onClosed(...) -- (void)webSocket:(__unused RCTSRWebSocket *)webSocket +- (void)webSocket:(__unused SRWebSocket *)webSocket didCloseWithCode:(__unused NSInteger)code reason:(__unused NSString *)reason wasClean:(__unused BOOL)wasClean @@ -244,9 +244,9 @@ - (void)connect } // The corresponding android code has a lot of custom config options for - // timeouts, but it appears the iOS RCTSRWebSocket API doesn't have the same - // implemented options. - _webSocket = [[RCTSRWebSocket alloc] initWithURL:_url]; + // timeouts, but our previous class, RCTSRWebSocket didn't have the same + // implemented options. Might be worth reinvestigating for SRWebSocket? + _webSocket = [[SRWebSocket alloc] initWithURL:_url]; [_webSocket setDelegateDispatchQueue:_jsQueue]; _webSocket.delegate = self; [_webSocket open]; @@ -290,7 +290,7 @@ - (void)sendToPackager:(NSDictionary *)messageObject if (error) { RCTLogWarn(@"Couldn't send event to packager: %@", error); } else { - [strongSelf->_webSocket send:messageText]; + [strongSelf->_webSocket sendString:messageText error:nil]; } } }); diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 419ae04c7f39e8..caa03ffdb814c1 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -124,6 +124,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/CoreModulesHeaders (1000.0.0): - glog @@ -133,6 +134,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/Default (1000.0.0): - glog @@ -141,6 +143,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/DevSupport (1000.0.0): - glog @@ -152,6 +155,7 @@ PODS: - React-jsiexecutor (= 1000.0.0) - React-jsinspector (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTActionSheetHeaders (1000.0.0): - glog @@ -161,6 +165,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTAnimationHeaders (1000.0.0): - glog @@ -170,6 +175,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTBlobHeaders (1000.0.0): - glog @@ -179,6 +185,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTImageHeaders (1000.0.0): - glog @@ -188,6 +195,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTLinkingHeaders (1000.0.0): - glog @@ -197,6 +205,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTNetworkHeaders (1000.0.0): - glog @@ -206,6 +215,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTPushNotificationHeaders (1000.0.0): - glog @@ -215,6 +225,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTSettingsHeaders (1000.0.0): - glog @@ -224,6 +235,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTTextHeaders (1000.0.0): - glog @@ -233,6 +245,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTVibrationHeaders (1000.0.0): - glog @@ -242,6 +255,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTWebSocket (1000.0.0): - glog @@ -251,6 +265,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - SocketRocket (= 0.6.0) - Yoga - React-CoreModules (1000.0.0): - RCT-Folly (= 2021.06.28.00-v2) @@ -260,6 +275,7 @@ PODS: - React-jsi (= 1000.0.0) - React-RCTImage (= 1000.0.0) - ReactCommon/turbomodule/core (= 1000.0.0) + - SocketRocket (= 0.6.0) - React-cxxreact (1000.0.0): - boost (= 1.76.0) - DoubleConversion @@ -563,8 +579,8 @@ SPEC CHECKSUMS: boost: 613e39eac4239cc72b15421247b5ab05361266a2 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: ed15e075aa758ac0e4c1f8b830bd4e4d40d669e8 - FBLazyVector: 384527e08219663bcf4929e2d266ae2a7ee355c9 - FBReactNativeSpec: 3ab1f6402e4c0cb609a2dd69ee5388553665a4ce + FBLazyVector: 05f55d9e5f5381d3a680921e02724c2b13b76e53 + FBReactNativeSpec: 0d24236bb9cf4fb9cfa91667b5954eaa5904e95b Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 57ffbe81ef95306cc9e69c4aa3aeeeeb58a6a28c @@ -580,39 +596,39 @@ SPEC CHECKSUMS: OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: 24c6da766832002a4a2aac5f79ee0ca50fbe8507 - RCTRequired: c227d1cbf4257746e1984626e164dd070135ab51 - RCTTypeSafety: cda767ff31bb0dcdeac45b4a1a2d04689a7a8e5a - React: fdeb98299a820dbec6cbb1e4c004fa3799dd376f - React-callinvoker: 8f64ce3fa5d782daa2fbee4cd4821698aaf673f1 + RCTRequired: 6b02089950eb599a3c667123b6ed6d3df3a14434 + RCTTypeSafety: 8a93937f909ab86b9239adc45ba31895a0a672d8 + React: 7423be55b3ff32a2f62edaf871d270ad2523edc6 + React-callinvoker: 35a708319bf51de76d43a6d204767c2ae793705a React-Codegen: 61a79489c164568e1575cc662fa7c9a167215dcc - React-Core: d26ae99463cddeadc5639612ca30616561415789 - React-CoreModules: cfce02ad8ea53a2295e1dac937eba5a90622a2bb - React-cxxreact: 12fd22c456355c71e5c5081dcffea1f32cae5773 - React-jsi: ae9bc2bfc04c6ff51b2cb72f57d0ae3ceed45ade - React-jsiexecutor: d58c93c836f125efe1dd5a6a4e67e91e22e879d2 - React-jsinspector: d42e72bb4097e204fb58e0dfa6a73064d5110350 - React-logger: 582ddca30294da58d26d7722038868e016907212 - React-perflogger: 3a2074e543aec103be610bac726ce08d7bca0a73 - React-RCTActionSheet: 2bd2272f1da5984e560ee2ff21385b252faea787 - React-RCTAnimation: 91bb2eef31ddb8254b440e9e270a379b724d42e7 - React-RCTBlob: 80f2a58eb43ed52347c74862396d5f782a43f1a7 - React-RCTImage: 072355172731a4e98969d8edc3429b34c66145d3 - React-RCTLinking: a453010e6eed69a6040466df1d027568f2614682 - React-RCTNetwork: 359c5d7fcab48d6ce482e1d41d18b41aef1c88ed - React-RCTPushNotification: 10ce9158a757b7b421e1ec859075c2b62a660c8c - React-RCTSettings: 814b8cd77826d9f701f5a405615af447f2cdb2cf - React-RCTTest: acb0c4b891472b017d6ad5b467677e09cc04fbcc - React-RCTText: 738955a75c0377dfca55de5c5f5197fb7ea2de46 - React-RCTVibration: 8ddcc5d106204b03a2b6cb7312dfb266c411c6f0 - React-runtimeexecutor: 406c4f19aaad1b5c000cb035b32b6abea250775d + React-Core: 8a687ee3c65b718dadf40ed42173099bdbb623d3 + React-CoreModules: fe4337869374c5c5e1df2ed819002568a7287819 + React-cxxreact: afab7429740e3b8422fded68cb303fa38f823a60 + React-jsi: e3322a77ad6087023ed7286c3b8b38cfd261a10d + React-jsiexecutor: 501b36015d0af2dcdc5f068a9d86e8a661bab0af + React-jsinspector: 3ac9584f1cb18acf9e71ce553b96999f8db3cfc6 + React-logger: 198f604475799f7a595efd88acda524b206d982e + React-perflogger: 3ce0bd35b6b8d729407614e1a4e5e0083b5889d4 + React-RCTActionSheet: c37baf42369050b28b593a9bc066deb30129f8c4 + React-RCTAnimation: 295515c1373370bac1f3f332877e044e9e59a7e3 + React-RCTBlob: e2c0dbb0adce405f856e660f92ebc5b8a2ddf272 + React-RCTImage: 376339dcfd6492b7b7bd7401c04061f437c48a77 + React-RCTLinking: 4363a76c9bd9761fdef7d953fce7bacbd2af4017 + React-RCTNetwork: 4b87f3b59b8b2aa5cba383f7955eda377f005505 + React-RCTPushNotification: 1089596a55781a09ed00f40939145e42cf77e056 + React-RCTSettings: c1aa6f089948e624b218cdfa294881c5327a257f + React-RCTTest: 19c04362cb1059b0c016ebee837590a1a951c9f8 + React-RCTText: b2d031835aea1401d5756e94ae43386de534d548 + React-RCTVibration: c0b52f3f7278db2b7b1ec8e188fdabc36d0e5239 + React-runtimeexecutor: 26bfeb1ba7b9225e888d8aab24eb63329bef6db2 React-TurboModuleCxx-RNW: 881411415eafe818f9cc3c4016167cc3d219402b - React-TurboModuleCxx-WinRTPort: 9183595ebeacb9692054a21fe62f675a0a255e8a - ReactCommon: fe52e3136583b019573d6d91521fc9c65bb8a64d - ScreenshotManager: f08c5d98fb598cd3cb2c2b28bca6862564fbed22 + React-TurboModuleCxx-WinRTPort: a8cbc650d49f499ea6ffe540711f9a0dc800da52 + ReactCommon: 287ec91cb37992ede30c232262f4d0d6264bc4af + ScreenshotManager: 1d5d672b71a31623453916311ff5656e0a6d4cfd SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - Yoga: 3b4464e1119acaf12514b1bfd1c9c874d216fe5f + Yoga: 8c464bb25cc3b0665f9cbae706e68f3e7fe97e42 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: b596acab1b1d65648cc41a960b7fac224c9d1905 +PODFILE CHECKSUM: 499c733d177ed6083eb15eefd24645ea8702ab82 COCOAPODS: 1.11.3