mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Make RCTWebSocketModule TurboModule-compatible
Summary: Changelog: [iOS][Added] - Make RCTWebSocketModule TurboModule-compatible Reviewed By: PeteTheHeat Differential Revision: D18353766 fbshipit-source-id: fde0f6593dd203ab3dcb8f9cf40012ba4761d6ba
This commit is contained in:
committed by
Facebook Github Bot
parent
1b2992e8b3
commit
d73ae1baa3
@@ -2613,12 +2613,6 @@ namespace facebook {
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@implementation RCTCxxConvert (NativeWebSocketModule_SpecConnectOptionsHeaders)
|
||||
+ (RCTManagedPointer *)JS_NativeWebSocketModule_SpecConnectOptionsHeaders:(id)json
|
||||
{
|
||||
return facebook::react::managedPointer<JS::NativeWebSocketModule::SpecConnectOptionsHeaders>(json);
|
||||
}
|
||||
@end
|
||||
@implementation RCTCxxConvert (NativeWebSocketModule_SpecConnectOptions)
|
||||
+ (RCTManagedPointer *)JS_NativeWebSocketModule_SpecConnectOptions:(id)json
|
||||
{
|
||||
@@ -2634,11 +2628,11 @@ namespace facebook {
|
||||
}
|
||||
|
||||
static facebook::jsi::Value __hostFunction_NativeWebSocketModuleSpecJSI_send(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
||||
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "send", @selector(send:socketID:), args, count);
|
||||
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "send", @selector(send:forSocketID:), args, count);
|
||||
}
|
||||
|
||||
static facebook::jsi::Value __hostFunction_NativeWebSocketModuleSpecJSI_sendBinary(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
||||
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "sendBinary", @selector(sendBinary:socketID:), args, count);
|
||||
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "sendBinary", @selector(sendBinary:forSocketID:), args, count);
|
||||
}
|
||||
|
||||
static facebook::jsi::Value __hostFunction_NativeWebSocketModuleSpecJSI_ping(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
||||
|
||||
@@ -2697,26 +2697,10 @@ namespace facebook {
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
namespace JS {
|
||||
namespace NativeWebSocketModule {
|
||||
struct SpecConnectOptionsHeaders {
|
||||
NSString *origin() const;
|
||||
|
||||
SpecConnectOptionsHeaders(NSDictionary *const v) : _v(v) {}
|
||||
private:
|
||||
NSDictionary *_v;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@interface RCTCxxConvert (NativeWebSocketModule_SpecConnectOptionsHeaders)
|
||||
+ (RCTManagedPointer *)JS_NativeWebSocketModule_SpecConnectOptionsHeaders:(id)json;
|
||||
@end
|
||||
|
||||
namespace JS {
|
||||
namespace NativeWebSocketModule {
|
||||
struct SpecConnectOptions {
|
||||
folly::Optional<JS::NativeWebSocketModule::SpecConnectOptionsHeaders> headers() const;
|
||||
id<NSObject> _Nullable headers() const;
|
||||
|
||||
SpecConnectOptions(NSDictionary *const v) : _v(v) {}
|
||||
private:
|
||||
@@ -2735,9 +2719,9 @@ namespace JS {
|
||||
options:(JS::NativeWebSocketModule::SpecConnectOptions &)options
|
||||
socketID:(double)socketID;
|
||||
- (void)send:(NSString *)message
|
||||
socketID:(double)socketID;
|
||||
forSocketID:(double)forSocketID;
|
||||
- (void)sendBinary:(NSString *)base64String
|
||||
socketID:(double)socketID;
|
||||
forSocketID:(double)forSocketID;
|
||||
- (void)ping:(double)socketID;
|
||||
- (void)close:(double)code
|
||||
reason:(NSString *)reason
|
||||
@@ -3738,13 +3722,8 @@ inline JS::NativeUIManager::Constants::Builder::Builder(const Input i) : _factor
|
||||
inline JS::NativeUIManager::Constants::Builder::Builder(Constants i) : _factory(^{
|
||||
return i.unsafeRawValue();
|
||||
}) {}
|
||||
inline NSString *JS::NativeWebSocketModule::SpecConnectOptionsHeaders::origin() const
|
||||
{
|
||||
id const p = _v[@"origin"];
|
||||
return RCTBridgingToString(p);
|
||||
}
|
||||
inline folly::Optional<JS::NativeWebSocketModule::SpecConnectOptionsHeaders> JS::NativeWebSocketModule::SpecConnectOptions::headers() const
|
||||
inline id<NSObject> _Nullable JS::NativeWebSocketModule::SpecConnectOptions::headers() const
|
||||
{
|
||||
id const p = _v[@"headers"];
|
||||
return (p == nil ? folly::none : folly::make_optional(JS::NativeWebSocketModule::SpecConnectOptionsHeaders(p)));
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
@@ -17,11 +17,11 @@ export interface Spec extends TurboModule {
|
||||
+connect: (
|
||||
url: string,
|
||||
protocols: ?Array<string>,
|
||||
options: ?{headers?: {origin?: string}},
|
||||
options: {|headers?: Object|},
|
||||
socketID: number,
|
||||
) => void;
|
||||
+send: (message: string, socketID: number) => void;
|
||||
+sendBinary: (base64String: string, socketID: number) => void;
|
||||
+send: (message: string, forSocketID: number) => void;
|
||||
+sendBinary: (base64String: string, forSocketID: number) => void;
|
||||
+ping: (socketID: number) => void;
|
||||
+close: (code: number, reason: string, socketID: number) => void;
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTDefines.h>
|
||||
#import <React/RCTJavaScriptExecutor.h>
|
||||
|
||||
#if RCT_DEV // Debug executors are only supported in dev mode
|
||||
|
||||
@interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)URL;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -1,273 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTWebSocketExecutor.h>
|
||||
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTDefines.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import <React/RCTSRWebSocket.h>
|
||||
|
||||
#if RCT_DEV // Debug executors are only supported in dev mode
|
||||
|
||||
typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary<NSString *, id> *reply);
|
||||
|
||||
@interface RCTWebSocketExecutor () <RCTSRWebSocketDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTWebSocketExecutor
|
||||
{
|
||||
RCTSRWebSocket *_socket;
|
||||
dispatch_queue_t _jsQueue;
|
||||
NSMutableDictionary<NSNumber *, RCTWSMessageCallback> *_callbacks;
|
||||
dispatch_semaphore_t _socketOpenSemaphore;
|
||||
NSMutableDictionary<NSString *, NSString *> *_injectedObjects;
|
||||
NSURL *_url;
|
||||
NSError *_setupError;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)URL
|
||||
{
|
||||
RCTAssertParam(URL);
|
||||
|
||||
if ((self = [self init])) {
|
||||
_url = URL;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
if (!_url) {
|
||||
NSInteger port = [[[_bridge bundleURL] port] integerValue] ?: RCT_METRO_PORT;
|
||||
NSString *host = [[_bridge bundleURL] host] ?: @"localhost";
|
||||
NSString *URLString = [NSString stringWithFormat:@"http://%@:%lld/debugger-proxy?role=client", host, (long long)port];
|
||||
_url = [RCTConvert NSURL:URLString];
|
||||
}
|
||||
|
||||
_jsQueue = dispatch_queue_create("com.facebook.react.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
|
||||
_socket = [[RCTSRWebSocket alloc] initWithURL:_url];
|
||||
_socket.delegate = self;
|
||||
_callbacks = [NSMutableDictionary new];
|
||||
_injectedObjects = [NSMutableDictionary new];
|
||||
[_socket setDelegateDispatchQueue:_jsQueue];
|
||||
|
||||
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-js-devtools" relativeToURL:_url];
|
||||
|
||||
NSURLSession *session = [NSURLSession sharedSession];
|
||||
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:startDevToolsURL]
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){}];
|
||||
[dataTask resume];
|
||||
if (![self connectToProxy]) {
|
||||
[self invalidate];
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger retries = 3;
|
||||
BOOL runtimeIsReady = [self prepareJSRuntime];
|
||||
while (!runtimeIsReady && retries > 0) {
|
||||
runtimeIsReady = [self prepareJSRuntime];
|
||||
retries--;
|
||||
}
|
||||
if (!runtimeIsReady) {
|
||||
[self invalidate];
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (BOOL)prepareJSRuntime
|
||||
{
|
||||
__block NSError *initError;
|
||||
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
||||
[self sendMessage:@{@"method": @"prepareJSRuntime"} onReply:^(NSError *error, NSDictionary<NSString *, id> *reply) {
|
||||
initError = error;
|
||||
dispatch_semaphore_signal(s);
|
||||
}];
|
||||
long runtimeIsReady = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10));
|
||||
if (initError) {
|
||||
RCTLogInfo(@"Websocket runtime setup failed: %@", initError);
|
||||
}
|
||||
return runtimeIsReady == 0 && initError == nil;
|
||||
}
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
||||
{
|
||||
NSError *error = nil;
|
||||
NSDictionary<NSString *, id> *reply = RCTJSONParse(message, &error);
|
||||
NSNumber *messageID = reply[@"replyID"];
|
||||
RCTWSMessageCallback callback = _callbacks[messageID];
|
||||
if (callback) {
|
||||
callback(error, reply);
|
||||
[_callbacks removeObjectForKey:messageID];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
||||
{
|
||||
dispatch_semaphore_signal(_socketOpenSemaphore);
|
||||
}
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
||||
{
|
||||
dispatch_semaphore_signal(_socketOpenSemaphore);
|
||||
RCTLogInfo(@"WebSocket connection failed with error %@", error);
|
||||
}
|
||||
|
||||
- (void)sendMessage:(NSDictionary<NSString *, id> *)message onReply:(RCTWSMessageCallback)callback
|
||||
{
|
||||
static NSUInteger lastID = 10000;
|
||||
|
||||
if (_setupError) {
|
||||
callback(_setupError, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(_jsQueue, ^{
|
||||
if (!self.valid) {
|
||||
callback(RCTErrorWithMessage(@"Runtime is not ready for debugging. Make sure Packager server is running."), nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *expectedID = @(lastID++);
|
||||
self->_callbacks[expectedID] = [callback copy];
|
||||
NSMutableDictionary<NSString *, id> *messageWithID = [message mutableCopy];
|
||||
messageWithID[@"id"] = expectedID;
|
||||
[self->_socket send:RCTJSONStringify(messageWithID, NULL)];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
// 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);
|
||||
|
||||
NSDictionary<NSString *, id> *message = @{
|
||||
@"method": @"executeApplicationScript",
|
||||
@"url": RCTNullIfNil(URL.absoluteString),
|
||||
@"inject": _injectedObjects,
|
||||
};
|
||||
[self sendMessage:message onReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
||||
if (socketError) {
|
||||
onComplete(socketError);
|
||||
} else {
|
||||
NSString *error = reply[@"error"];
|
||||
onComplete(error ? RCTErrorWithMessage(error) : nil);
|
||||
}
|
||||
dispatch_semaphore_signal(scriptSem);
|
||||
}];
|
||||
|
||||
dispatch_semaphore_wait(scriptSem, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
||||
NSDictionary<NSString *, id> *message = @{
|
||||
@"method": method,
|
||||
@"arguments": arguments
|
||||
};
|
||||
[self sendMessage:message onReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
||||
if (socketError) {
|
||||
onComplete(nil, socketError);
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *jsonError;
|
||||
id result = RCTJSONParse(reply[@"result"], &jsonError);
|
||||
NSString *error = reply[@"error"];
|
||||
onComplete(result, error ? RCTErrorWithMessage(error) : jsonError);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
dispatch_async(_jsQueue, ^{
|
||||
self->_injectedObjects[objectName] = script;
|
||||
onComplete(nil);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
RCTExecuteOnMainQueue(block);
|
||||
}
|
||||
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_socket.delegate = nil;
|
||||
[_socket closeWithCode:1000 reason:@"Invalidated"];
|
||||
_socket = nil;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _socket != nil && _socket.readyState == RCTSR_OPEN;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol RCTWebSocketContentHandler <NSObject>
|
||||
|
||||
- (id)processWebsocketMessage:(id __nullable)message
|
||||
forSocketID:(NSNumber *)socketID
|
||||
withType:(NSString *__nonnull __autoreleasing *__nonnull)type;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTWebSocketModule : RCTEventEmitter
|
||||
|
||||
// Register a custom handler for a specific websocket. The handler will be strongly held by the WebSocketModule.
|
||||
- (void)setContentHandler:(id<RCTWebSocketContentHandler> __nullable)handler forSocketID:(NSNumber *)socketID;
|
||||
|
||||
- (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTWebSocketModule)
|
||||
|
||||
- (RCTWebSocketModule *)webSocketModule;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTWebSocketModule.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import <React/RCTSRWebSocket.h>
|
||||
|
||||
@implementation RCTSRWebSocket (React)
|
||||
|
||||
- (NSNumber *)reactTag
|
||||
{
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setReactTag:(NSNumber *)reactTag
|
||||
{
|
||||
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTWebSocketModule () <RCTSRWebSocketDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTWebSocketModule
|
||||
{
|
||||
NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
|
||||
NSMutableDictionary<NSNumber *, id<RCTWebSocketContentHandler>> *_contentHandlers;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
- (NSArray *)supportedEvents
|
||||
{
|
||||
return @[@"websocketMessage",
|
||||
@"websocketOpen",
|
||||
@"websocketFailed",
|
||||
@"websocketClosed"];
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_contentHandlers = nil;
|
||||
for (RCTSRWebSocket *socket in _sockets.allValues) {
|
||||
socket.delegate = nil;
|
||||
[socket close];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols options:(NSDictionary *)options socketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
||||
|
||||
// We load cookies from sharedHTTPCookieStorage (shared with XHR and
|
||||
// fetch). To get secure cookies for wss URLs, replace wss with https
|
||||
// in the URL.
|
||||
NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:true];
|
||||
if ([components.scheme.lowercaseString isEqualToString:@"wss"]) {
|
||||
components.scheme = @"https";
|
||||
}
|
||||
|
||||
// Load and set the cookie header.
|
||||
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:components.URL];
|
||||
request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
||||
|
||||
// Load supplied headers
|
||||
[options[@"headers"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
||||
[request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
|
||||
}];
|
||||
|
||||
RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:request protocols:protocols];
|
||||
[webSocket setDelegateDispatchQueue:[self methodQueue]];
|
||||
webSocket.delegate = self;
|
||||
webSocket.reactTag = socketID;
|
||||
if (!_sockets) {
|
||||
_sockets = [NSMutableDictionary new];
|
||||
}
|
||||
_sockets[socketID] = webSocket;
|
||||
[webSocket open];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(send:(NSString *)message forSocketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[_sockets[socketID] send:message];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(sendBinary:(NSString *)base64String forSocketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[self sendData:[[NSData alloc] initWithBase64EncodedString:base64String options:0] forSocketID:socketID];
|
||||
}
|
||||
|
||||
- (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID
|
||||
{
|
||||
[_sockets[socketID] send:data];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(ping:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[_sockets[socketID] sendPing:NULL];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(close:(NSInteger)code reason:(NSString *)reason socketID:(nonnull NSNumber *)socketID)
|
||||
{
|
||||
[_sockets[socketID] closeWithCode:code reason:reason];
|
||||
[_sockets removeObjectForKey:socketID];
|
||||
}
|
||||
|
||||
- (void)setContentHandler:(id<RCTWebSocketContentHandler>)handler forSocketID:(NSString *)socketID
|
||||
{
|
||||
if (!_contentHandlers) {
|
||||
_contentHandlers = [NSMutableDictionary new];
|
||||
}
|
||||
_contentHandlers[socketID] = handler;
|
||||
}
|
||||
|
||||
#pragma mark - RCTSRWebSocketDelegate methods
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
||||
{
|
||||
NSString *type;
|
||||
|
||||
NSNumber *socketID = [webSocket reactTag];
|
||||
id contentHandler = _contentHandlers[socketID];
|
||||
if (contentHandler) {
|
||||
message = [contentHandler processWebsocketMessage:message forSocketID:socketID withType:&type];
|
||||
} else {
|
||||
if ([message isKindOfClass:[NSData class]]) {
|
||||
type = @"binary";
|
||||
message = [message base64EncodedStringWithOptions:0];
|
||||
} else {
|
||||
type = @"text";
|
||||
}
|
||||
}
|
||||
|
||||
[self sendEventWithName:@"websocketMessage" body:@{
|
||||
@"data": message,
|
||||
@"type": type,
|
||||
@"id": webSocket.reactTag
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
||||
{
|
||||
[self sendEventWithName:@"websocketOpen" body:@{
|
||||
@"id": webSocket.reactTag,
|
||||
@"protocol": webSocket.protocol ? webSocket.protocol : @""
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
||||
{
|
||||
NSNumber *socketID = [webSocket reactTag];
|
||||
_contentHandlers[socketID] = nil;
|
||||
_sockets[socketID] = nil;
|
||||
[self sendEventWithName:@"websocketFailed" body:@{
|
||||
@"message": error.localizedDescription,
|
||||
@"id": socketID
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket
|
||||
didCloseWithCode:(NSInteger)code
|
||||
reason:(NSString *)reason
|
||||
wasClean:(BOOL)wasClean
|
||||
{
|
||||
NSNumber *socketID = [webSocket reactTag];
|
||||
_contentHandlers[socketID] = nil;
|
||||
_sockets[socketID] = nil;
|
||||
[self sendEventWithName:@"websocketClosed" body:@{
|
||||
@"code": @(code),
|
||||
@"reason": RCTNullIfNil(reason),
|
||||
@"clean": @(wasClean),
|
||||
@"id": socketID
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTWebSocketModule)
|
||||
|
||||
- (RCTWebSocketModule *)webSocketModule
|
||||
{
|
||||
return [self moduleForClass:[RCTWebSocketModule class]];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user