2015-05-14 16:28:09 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
|
|
|
|
2015-10-07 15:28:34 +00:00
|
|
|
#import "RCTWebSocketModule.h"
|
2015-05-14 16:28:09 +00:00
|
|
|
|
2016-07-18 14:12:19 +00:00
|
|
|
#import <objc/runtime.h>
|
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import <React/RCTUtils.h>
|
|
|
|
|
|
|
|
#import "RCTSRWebSocket.h"
|
2015-05-14 16:28:09 +00:00
|
|
|
|
|
|
|
@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
|
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
@interface RCTWebSocketModule () <RCTSRWebSocketDelegate>
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-10-07 15:28:34 +00:00
|
|
|
@implementation RCTWebSocketModule
|
2015-05-14 16:28:09 +00:00
|
|
|
{
|
2017-07-26 15:12:12 +00:00
|
|
|
NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
|
|
|
|
NSMutableDictionary<NSNumber *, id> *_contentHandlers;
|
2015-05-14 16:28:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2017-07-26 15:12:12 +00:00
|
|
|
// Used by RCTBlobModule
|
|
|
|
@synthesize methodQueue = _methodQueue;
|
|
|
|
|
2016-05-12 15:29:39 +00:00
|
|
|
- (NSArray *)supportedEvents
|
|
|
|
{
|
|
|
|
return @[@"websocketMessage",
|
|
|
|
@"websocketOpen",
|
|
|
|
@"websocketFailed",
|
|
|
|
@"websocketClosed"];
|
|
|
|
}
|
2015-05-14 16:28:09 +00:00
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
for (RCTSRWebSocket *socket in _sockets.allValues) {
|
2015-05-14 16:28:09 +00:00
|
|
|
socket.delegate = nil;
|
|
|
|
[socket close];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
WebSocket API change to make room for other connection options (SSL pinning)
Summary:
This is a simple groundwork PR to allow options to be passed to the `WebSocket` constructor. It represents a minor change to an undocumented part of the API, moving `headers` to within `options`.
This will be a BC for anyone manually specifying headers other than `origin` but a) that's not a common use case with WebSockets and b) it's not documented even in code and wouldn't currently pass a flow check.
NB: The third argument to the WebSocket constructor isn't part of the W3C spec, so I think this is a good place for RN-specific named parameters, better than adding a fourth argument. `protocols` needs to stay where it is, in line with the spec.
If this goes through I'd like to build on it by adding an additional connection option for SSL certificate pinning, as already supported by the underlying `okhttp` and `RCTSRWebSocket`. It could later be expanded for various other uses.
Currently, there's no way for a `WebSocket` user to specify any connection options other than url, protocol and headers. The fact that `WebSocket` connects in its constructor means any options have to go in there.
Connect to a websocket server using iOS and Android, observe the connection headers:
1. Without specifying `origin`, the default header should be set
2. Specifying it in the old way `new WebSocket(url, protocols, { origin: 'customorigin.com' })`
3. Specifying it in the new way `new WebSocket(url, protocols, { headers: { origin: 'customorigin.com' }})`.
I've tested myself using the test app with iOS and Android.
Closes https://github.com/facebook/react-native/pull/15334
Differential Revision: D5601675
Pulled By: javache
fbshipit-source-id: 5959d03a3e1d269b2c6775f3e0cf071ff08617bf
2017-08-10 12:59:36 +00:00
|
|
|
RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols options:(NSDictionary *)options socketID:(nonnull NSNumber *)socketID)
|
2015-05-14 16:28:09 +00:00
|
|
|
{
|
2016-03-15 18:48:42 +00:00
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
2017-07-26 15:12:12 +00:00
|
|
|
|
2017-05-24 16:36:40 +00:00
|
|
|
// 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];
|
2017-07-26 15:12:12 +00:00
|
|
|
|
2017-05-24 16:36:40 +00:00
|
|
|
// Load supplied headers
|
WebSocket API change to make room for other connection options (SSL pinning)
Summary:
This is a simple groundwork PR to allow options to be passed to the `WebSocket` constructor. It represents a minor change to an undocumented part of the API, moving `headers` to within `options`.
This will be a BC for anyone manually specifying headers other than `origin` but a) that's not a common use case with WebSockets and b) it's not documented even in code and wouldn't currently pass a flow check.
NB: The third argument to the WebSocket constructor isn't part of the W3C spec, so I think this is a good place for RN-specific named parameters, better than adding a fourth argument. `protocols` needs to stay where it is, in line with the spec.
If this goes through I'd like to build on it by adding an additional connection option for SSL certificate pinning, as already supported by the underlying `okhttp` and `RCTSRWebSocket`. It could later be expanded for various other uses.
Currently, there's no way for a `WebSocket` user to specify any connection options other than url, protocol and headers. The fact that `WebSocket` connects in its constructor means any options have to go in there.
Connect to a websocket server using iOS and Android, observe the connection headers:
1. Without specifying `origin`, the default header should be set
2. Specifying it in the old way `new WebSocket(url, protocols, { origin: 'customorigin.com' })`
3. Specifying it in the new way `new WebSocket(url, protocols, { headers: { origin: 'customorigin.com' }})`.
I've tested myself using the test app with iOS and Android.
Closes https://github.com/facebook/react-native/pull/15334
Differential Revision: D5601675
Pulled By: javache
fbshipit-source-id: 5959d03a3e1d269b2c6775f3e0cf071ff08617bf
2017-08-10 12:59:36 +00:00
|
|
|
[options[@"headers"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
2016-03-15 18:48:42 +00:00
|
|
|
[request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
|
|
|
|
}];
|
|
|
|
|
|
|
|
RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:request protocols:protocols];
|
2015-05-14 16:28:09 +00:00
|
|
|
webSocket.delegate = self;
|
|
|
|
webSocket.reactTag = socketID;
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_sockets) {
|
|
|
|
_sockets = [NSMutableDictionary new];
|
|
|
|
}
|
2015-05-14 16:28:09 +00:00
|
|
|
_sockets[socketID] = webSocket;
|
|
|
|
[webSocket open];
|
|
|
|
}
|
|
|
|
|
2017-07-26 15:12:12 +00:00
|
|
|
RCT_EXPORT_METHOD(send:(NSString *)message forSocketID:(nonnull NSNumber *)socketID)
|
2015-05-14 16:28:09 +00:00
|
|
|
{
|
|
|
|
[_sockets[socketID] send:message];
|
|
|
|
}
|
|
|
|
|
2017-07-26 15:12:12 +00:00
|
|
|
RCT_EXPORT_METHOD(sendBinary:(NSString *)base64String forSocketID:(nonnull NSNumber *)socketID)
|
2016-04-20 15:52:22 +00:00
|
|
|
{
|
2017-07-26 15:12:12 +00:00
|
|
|
[self sendData:[[NSData alloc] initWithBase64EncodedString:base64String options:0] forSocketID:socketID];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID
|
|
|
|
{
|
|
|
|
[_sockets[socketID] send:data];
|
2016-04-20 15:52:22 +00:00
|
|
|
}
|
|
|
|
|
2016-07-05 12:52:24 +00:00
|
|
|
RCT_EXPORT_METHOD(ping:(nonnull NSNumber *)socketID)
|
|
|
|
{
|
|
|
|
[_sockets[socketID] sendPing:NULL];
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID)
|
2015-05-14 16:28:09 +00:00
|
|
|
{
|
|
|
|
[_sockets[socketID] close];
|
2015-11-14 18:25:00 +00:00
|
|
|
[_sockets removeObjectForKey:socketID];
|
2015-05-14 16:28:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 15:12:12 +00:00
|
|
|
- (void)setContentHandler:(id<RCTWebSocketContentHandler>)handler forSocketID:(NSString *)socketID
|
|
|
|
{
|
|
|
|
if (!_contentHandlers) {
|
|
|
|
_contentHandlers = [NSMutableDictionary new];
|
|
|
|
}
|
|
|
|
_contentHandlers[socketID] = handler;
|
|
|
|
}
|
|
|
|
|
2015-05-14 16:28:09 +00:00
|
|
|
#pragma mark - RCTSRWebSocketDelegate methods
|
|
|
|
|
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
|
|
|
{
|
2017-07-26 15:12:12 +00:00
|
|
|
NSString *type;
|
|
|
|
|
|
|
|
NSNumber *socketID = [webSocket reactTag];
|
|
|
|
id contentHandler = _contentHandlers[socketID];
|
|
|
|
if (contentHandler) {
|
|
|
|
message = [contentHandler processMessage:message forSocketID:socketID withType:&type];
|
|
|
|
} else {
|
|
|
|
if ([message isKindOfClass:[NSData class]]) {
|
|
|
|
type = @"binary";
|
|
|
|
message = [message base64EncodedStringWithOptions:0];
|
|
|
|
} else {
|
|
|
|
type = @"text";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-12 15:29:39 +00:00
|
|
|
[self sendEventWithName:@"websocketMessage" body:@{
|
2017-07-26 15:12:12 +00:00
|
|
|
@"data": message,
|
|
|
|
@"type": type,
|
2015-05-14 16:28:09 +00:00
|
|
|
@"id": webSocket.reactTag
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
|
|
|
{
|
2016-05-12 15:29:39 +00:00
|
|
|
[self sendEventWithName:@"websocketOpen" body:@{
|
2015-05-14 16:28:09 +00:00
|
|
|
@"id": webSocket.reactTag
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
|
|
|
{
|
2017-07-26 15:12:12 +00:00
|
|
|
NSNumber *socketID = [webSocket reactTag];
|
|
|
|
_contentHandlers[socketID] = nil;
|
2016-05-12 15:29:39 +00:00
|
|
|
[self sendEventWithName:@"websocketFailed" body:@{
|
2017-07-26 15:12:12 +00:00
|
|
|
@"message": error.localizedDescription,
|
|
|
|
@"id": socketID
|
2015-05-14 16:28:09 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2017-07-26 15:12:12 +00:00
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket
|
|
|
|
didCloseWithCode:(NSInteger)code
|
|
|
|
reason:(NSString *)reason
|
|
|
|
wasClean:(BOOL)wasClean
|
2015-05-14 16:28:09 +00:00
|
|
|
{
|
2017-07-26 15:12:12 +00:00
|
|
|
NSNumber *socketID = [webSocket reactTag];
|
|
|
|
_contentHandlers[socketID] = nil;
|
2016-05-12 15:29:39 +00:00
|
|
|
[self sendEventWithName:@"websocketClosed" body:@{
|
2015-05-14 16:28:09 +00:00
|
|
|
@"code": @(code),
|
2015-06-12 18:05:01 +00:00
|
|
|
@"reason": RCTNullIfNil(reason),
|
2015-05-14 16:28:09 +00:00
|
|
|
@"clean": @(wasClean),
|
2017-07-26 15:12:12 +00:00
|
|
|
@"id": socketID
|
2015-05-14 16:28:09 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2017-07-26 15:12:12 +00:00
|
|
|
|
|
|
|
@implementation RCTBridge (RCTWebSocketModule)
|
|
|
|
|
|
|
|
- (RCTWebSocketModule *)webSocketModule
|
|
|
|
{
|
|
|
|
return [self moduleForClass:[RCTWebSocketModule class]];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|