1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | #import <React/RCTWebSocketModule.h>
|
9 |
|
10 | #import <objc/runtime.h>
|
11 |
|
12 | #import <FBReactNativeSpec/FBReactNativeSpec.h>
|
13 | #import <React/RCTConvert.h>
|
14 | #import <React/RCTUtils.h>
|
15 | #import <React/RCTSRWebSocket.h>
|
16 |
|
17 | #import "CoreModulesPlugins.h"
|
18 |
|
19 | @implementation RCTSRWebSocket (React)
|
20 |
|
21 | - (NSNumber *)reactTag
|
22 | {
|
23 | return objc_getAssociatedObject(self, _cmd);
|
24 | }
|
25 |
|
26 | - (void)setReactTag:(NSNumber *)reactTag
|
27 | {
|
28 | objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
29 | }
|
30 |
|
31 | @end
|
32 |
|
33 | @interface RCTWebSocketModule () <RCTSRWebSocketDelegate, NativeWebSocketModuleSpec>
|
34 |
|
35 | @end
|
36 |
|
37 | @implementation RCTWebSocketModule
|
38 | {
|
39 | NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
|
40 | NSMutableDictionary<NSNumber *, id<RCTWebSocketContentHandler>> *_contentHandlers;
|
41 | }
|
42 |
|
43 | RCT_EXPORT_MODULE()
|
44 |
|
45 | - (dispatch_queue_t)methodQueue
|
46 | {
|
47 | return dispatch_get_main_queue();
|
48 | }
|
49 |
|
50 | - (NSArray *)supportedEvents
|
51 | {
|
52 | return @[@"websocketMessage",
|
53 | @"websocketOpen",
|
54 | @"websocketFailed",
|
55 | @"websocketClosed"];
|
56 | }
|
57 |
|
58 | - (void)invalidate
|
59 | {
|
60 | _contentHandlers = nil;
|
61 | for (RCTSRWebSocket *socket in _sockets.allValues) {
|
62 | socket.delegate = nil;
|
63 | [socket close];
|
64 | }
|
65 | }
|
66 |
|
67 | RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols options:(JS::NativeWebSocketModule::SpecConnectOptions &)options socketID:(double)socketID)
|
68 | {
|
69 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:true];
|
75 | if ([components.scheme.lowercaseString isEqualToString:@"wss"]) {
|
76 | components.scheme = @"https";
|
77 | }
|
78 |
|
79 |
|
80 | NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:components.URL];
|
81 | request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
82 |
|
83 |
|
84 | if ([options.headers() isKindOfClass:NSDictionary.class]) {
|
85 | NSDictionary *headers = (NSDictionary *)options.headers();
|
86 | [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
87 | [request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
|
88 | }];
|
89 | }
|
90 |
|
91 | RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:request protocols:protocols];
|
92 | [webSocket setDelegateDispatchQueue:[self methodQueue]];
|
93 | webSocket.delegate = self;
|
94 | webSocket.reactTag = @(socketID);
|
95 | if (!_sockets) {
|
96 | _sockets = [NSMutableDictionary new];
|
97 | }
|
98 | _sockets[@(socketID)] = webSocket;
|
99 | [webSocket open];
|
100 | }
|
101 |
|
102 | RCT_EXPORT_METHOD(send:(NSString *)message forSocketID:(double)socketID)
|
103 | {
|
104 | [_sockets[@(socketID)] send:message];
|
105 | }
|
106 |
|
107 | RCT_EXPORT_METHOD(sendBinary:(NSString *)base64String forSocketID:(double)socketID)
|
108 | {
|
109 | [self sendData:[[NSData alloc] initWithBase64EncodedString:base64String options:0] forSocketID:@(socketID)];
|
110 | }
|
111 |
|
112 | - (void)sendData:(NSData *)data forSocketID:(NSNumber * __nonnull)socketID
|
113 | {
|
114 | [_sockets[socketID] send:data];
|
115 | }
|
116 |
|
117 | RCT_EXPORT_METHOD(ping:(double)socketID)
|
118 | {
|
119 | [_sockets[@(socketID)] sendPing:NULL];
|
120 | }
|
121 |
|
122 | RCT_EXPORT_METHOD(close:(double)code reason:(NSString *)reason socketID:(double)socketID)
|
123 | {
|
124 | [_sockets[@(socketID)] closeWithCode:code reason:reason];
|
125 | [_sockets removeObjectForKey:@(socketID)];
|
126 | }
|
127 |
|
128 | - (void)setContentHandler:(id<RCTWebSocketContentHandler>)handler forSocketID:(NSString *)socketID
|
129 | {
|
130 | if (!_contentHandlers) {
|
131 | _contentHandlers = [NSMutableDictionary new];
|
132 | }
|
133 | _contentHandlers[socketID] = handler;
|
134 | }
|
135 |
|
136 | #pragma mark - RCTSRWebSocketDelegate methods
|
137 |
|
138 | - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
139 | {
|
140 | NSString *type;
|
141 |
|
142 | NSNumber *socketID = [webSocket reactTag];
|
143 | id contentHandler = _contentHandlers[socketID];
|
144 | if (contentHandler) {
|
145 | message = [contentHandler processWebsocketMessage:message forSocketID:socketID withType:&type];
|
146 | } else {
|
147 | if ([message isKindOfClass:[NSData class]]) {
|
148 | type = @"binary";
|
149 | message = [message base64EncodedStringWithOptions:0];
|
150 | } else {
|
151 | type = @"text";
|
152 | }
|
153 | }
|
154 |
|
155 | [self sendEventWithName:@"websocketMessage" body:@{
|
156 | @"data": message,
|
157 | @"type": type,
|
158 | @"id": webSocket.reactTag
|
159 | }];
|
160 | }
|
161 |
|
162 | - (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
163 | {
|
164 | [self sendEventWithName:@"websocketOpen" body:@{
|
165 | @"id": webSocket.reactTag,
|
166 | @"protocol": webSocket.protocol ? webSocket.protocol : @""
|
167 | }];
|
168 | }
|
169 |
|
170 | - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
171 | {
|
172 | NSNumber *socketID = [webSocket reactTag];
|
173 | _contentHandlers[socketID] = nil;
|
174 | _sockets[socketID] = nil;
|
175 | [self sendEventWithName:@"websocketFailed" body:@{
|
176 | @"message": error.localizedDescription,
|
177 | @"id": socketID
|
178 | }];
|
179 | }
|
180 |
|
181 | - (void)webSocket:(RCTSRWebSocket *)webSocket
|
182 | didCloseWithCode:(NSInteger)code
|
183 | reason:(NSString *)reason
|
184 | wasClean:(BOOL)wasClean
|
185 | {
|
186 | NSNumber *socketID = [webSocket reactTag];
|
187 | _contentHandlers[socketID] = nil;
|
188 | _sockets[socketID] = nil;
|
189 | [self sendEventWithName:@"websocketClosed" body:@{
|
190 | @"code": @(code),
|
191 | @"reason": RCTNullIfNil(reason),
|
192 | @"clean": @(wasClean),
|
193 | @"id": socketID
|
194 | }];
|
195 | }
|
196 |
|
197 | - (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
198 | {
|
199 | return std::make_shared<facebook::react::NativeWebSocketModuleSpecJSI>(self, jsInvoker);
|
200 | }
|
201 |
|
202 | @end
|
203 |
|
204 | @implementation RCTBridge (RCTWebSocketModule)
|
205 |
|
206 | - (RCTWebSocketModule *)webSocketModule
|
207 | {
|
208 | return [self moduleForClass:[RCTWebSocketModule class]];
|
209 | }
|
210 |
|
211 | @end
|
212 |
|
213 | Class RCTWebSocketModuleCls(void) {
|
214 | return RCTWebSocketModule.class;
|
215 | }
|