RCTBlobManager.mm 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTBlobManager.h>
  8. #import <mutex>
  9. #import <FBReactNativeSpec/FBReactNativeSpec.h>
  10. #import <React/RCTConvert.h>
  11. #import <React/RCTNetworking.h>
  12. #import <React/RCTUtils.h>
  13. #import <React/RCTWebSocketModule.h>
  14. #import "RCTBlobPlugins.h"
  15. #import "RCTBlobCollector.h"
  16. static NSString *const kBlobURIScheme = @"blob";
  17. @interface RCTBlobManager () <RCTNetworkingRequestHandler, RCTNetworkingResponseHandler, RCTWebSocketContentHandler, NativeBlobModuleSpec>
  18. @end
  19. @implementation RCTBlobManager
  20. {
  21. // Blobs should be thread safe since they are used from the websocket and networking module,
  22. // make sure to use proper locking when accessing this.
  23. NSMutableDictionary<NSString *, NSData *> *_blobs;
  24. std::mutex _blobsMutex;
  25. NSOperationQueue *_queue;
  26. }
  27. RCT_EXPORT_MODULE(BlobModule)
  28. @synthesize bridge = _bridge;
  29. @synthesize methodQueue = _methodQueue;
  30. @synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate;
  31. - (void)setBridge:(RCTBridge *)bridge
  32. {
  33. _bridge = bridge;
  34. std::lock_guard<std::mutex> lock(_blobsMutex);
  35. _blobs = [NSMutableDictionary new];
  36. facebook::react::RCTBlobCollector::install(self);
  37. }
  38. + (BOOL)requiresMainQueueSetup
  39. {
  40. return NO;
  41. }
  42. - (NSDictionary<NSString *, id> *)constantsToExport
  43. {
  44. return [self getConstants];
  45. }
  46. - (NSDictionary<NSString *, id> *)getConstants
  47. {
  48. return @{
  49. @"BLOB_URI_SCHEME": kBlobURIScheme,
  50. @"BLOB_URI_HOST": [NSNull null],
  51. };
  52. }
  53. - (NSString *)store:(NSData *)data
  54. {
  55. NSString *blobId = [NSUUID UUID].UUIDString;
  56. [self store:data withId:blobId];
  57. return blobId;
  58. }
  59. - (void)store:(NSData *)data withId:(NSString *)blobId
  60. {
  61. std::lock_guard<std::mutex> lock(_blobsMutex);
  62. _blobs[blobId] = data;
  63. }
  64. - (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
  65. {
  66. NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
  67. NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
  68. NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
  69. return [self resolve:blobId
  70. offset:offset ? [offset integerValue] : 0
  71. size:size ? [size integerValue] : -1];
  72. }
  73. - (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
  74. {
  75. NSData *data;
  76. {
  77. std::lock_guard<std::mutex> lock(_blobsMutex);
  78. data = _blobs[blobId];
  79. }
  80. if (!data) {
  81. return nil;
  82. }
  83. if (offset != 0 || (size != -1 && size != data.length)) {
  84. data = [data subdataWithRange:NSMakeRange(offset, size)];
  85. }
  86. return data;
  87. }
  88. - (NSData *)resolveURL:(NSURL *)url
  89. {
  90. NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
  91. NSString *blobId = components.path;
  92. NSInteger offset = 0;
  93. NSInteger size = -1;
  94. if (components.queryItems) {
  95. for (NSURLQueryItem *queryItem in components.queryItems) {
  96. if ([queryItem.name isEqualToString:@"offset"]) {
  97. offset = [queryItem.value integerValue];
  98. }
  99. if ([queryItem.name isEqualToString:@"size"]) {
  100. size = [queryItem.value integerValue];
  101. }
  102. }
  103. }
  104. if (blobId) {
  105. return [self resolve:blobId offset:offset size:size];
  106. }
  107. return nil;
  108. }
  109. - (void)remove:(NSString *)blobId
  110. {
  111. std::lock_guard<std::mutex> lock(_blobsMutex);
  112. [_blobs removeObjectForKey:blobId];
  113. }
  114. RCT_EXPORT_METHOD(addNetworkingHandler)
  115. {
  116. RCTNetworking *const networking = _bridge ? _bridge.networking : [_turboModuleLookupDelegate moduleForName:"RCTNetworking"];
  117. // TODO(T63516227): Why can methodQueue be nil here?
  118. // We don't want to do anything when methodQueue is nil.
  119. if (!networking.methodQueue) {
  120. return;
  121. }
  122. dispatch_async(networking.methodQueue, ^{
  123. [networking addRequestHandler:self];
  124. [networking addResponseHandler:self];
  125. });
  126. }
  127. RCT_EXPORT_METHOD(addWebSocketHandler:(double)socketID)
  128. {
  129. dispatch_async(_bridge.webSocketModule.methodQueue, ^{
  130. [self->_bridge.webSocketModule setContentHandler:self forSocketID:[NSNumber numberWithDouble:socketID]];
  131. });
  132. }
  133. RCT_EXPORT_METHOD(removeWebSocketHandler:(double)socketID)
  134. {
  135. dispatch_async(_bridge.webSocketModule.methodQueue, ^{
  136. [self->_bridge.webSocketModule setContentHandler:nil forSocketID:[NSNumber numberWithDouble:socketID]];
  137. });
  138. }
  139. // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
  140. RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(double)socketID)
  141. {
  142. dispatch_async(_bridge.webSocketModule.methodQueue, ^{
  143. [self->_bridge.webSocketModule sendData:[self resolve:blob] forSocketID:[NSNumber numberWithDouble:socketID]];
  144. });
  145. }
  146. RCT_EXPORT_METHOD(createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId)
  147. {
  148. NSMutableData *data = [NSMutableData new];
  149. for (NSDictionary<NSString *, id> *part in parts) {
  150. NSString *type = [RCTConvert NSString:part[@"type"]];
  151. if ([type isEqualToString:@"blob"]) {
  152. NSData *partData = [self resolve:part[@"data"]];
  153. [data appendData:partData];
  154. } else if ([type isEqualToString:@"string"]) {
  155. NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
  156. [data appendData:partData];
  157. } else {
  158. [NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];
  159. }
  160. }
  161. [self store:data withId:blobId];
  162. }
  163. RCT_EXPORT_METHOD(release:(NSString *)blobId)
  164. {
  165. [self remove:blobId];
  166. }
  167. #pragma mark - RCTURLRequestHandler methods
  168. - (BOOL)canHandleRequest:(NSURLRequest *)request
  169. {
  170. return [request.URL.scheme caseInsensitiveCompare:kBlobURIScheme] == NSOrderedSame;
  171. }
  172. - (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
  173. {
  174. // Lazy setup
  175. if (!_queue) {
  176. _queue = [NSOperationQueue new];
  177. _queue.maxConcurrentOperationCount = 2;
  178. }
  179. __weak __typeof(self) weakSelf = self;
  180. __weak __block NSBlockOperation *weakOp;
  181. __block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
  182. __typeof(self) strongSelf = weakSelf;
  183. if (!strongSelf) {
  184. return;
  185. }
  186. NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
  187. MIMEType:nil
  188. expectedContentLength:-1
  189. textEncodingName:nil];
  190. [delegate URLRequest:weakOp didReceiveResponse:response];
  191. NSData *data = [strongSelf resolveURL:response.URL];
  192. NSError *error;
  193. if (data) {
  194. [delegate URLRequest:weakOp didReceiveData:data];
  195. } else {
  196. error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
  197. }
  198. [delegate URLRequest:weakOp didCompleteWithError:error];
  199. }];
  200. weakOp = op;
  201. [_queue addOperation:op];
  202. return op;
  203. }
  204. - (void)cancelRequest:(NSOperation *)op
  205. {
  206. [op cancel];
  207. }
  208. #pragma mark - RCTNetworkingRequestHandler methods
  209. // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
  210. - (BOOL)canHandleNetworkingRequest:(NSDictionary *)data
  211. {
  212. return data[@"blob"] != nil;
  213. }
  214. // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
  215. - (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data
  216. {
  217. // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
  218. NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]];
  219. NSString *contentType = @"application/octet-stream";
  220. NSString *blobType = [RCTConvert NSString:blob[@"type"]];
  221. if (blobType != nil && blobType.length > 0) {
  222. contentType = blob[@"type"];
  223. }
  224. return @{@"body": [self resolve:blob], @"contentType": contentType};
  225. }
  226. - (BOOL)canHandleNetworkingResponse:(NSString *)responseType
  227. {
  228. return [responseType isEqualToString:@"blob"];
  229. }
  230. - (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data
  231. {
  232. // An empty body will have nil for data, in this case we need to return
  233. // an empty blob as per the XMLHttpRequest spec.
  234. data = data ?: [NSData new];
  235. return @{
  236. @"blobId": [self store:data],
  237. @"offset": @0,
  238. @"size": @(data.length),
  239. @"name": RCTNullIfNil([response suggestedFilename]),
  240. @"type": RCTNullIfNil([response MIMEType]),
  241. };
  242. }
  243. #pragma mark - RCTWebSocketContentHandler methods
  244. - (id)processWebsocketMessage:(id)message
  245. forSocketID:(NSNumber *)socketID
  246. withType:(NSString *__autoreleasing _Nonnull *)type
  247. {
  248. if (![message isKindOfClass:[NSData class]]) {
  249. *type = @"text";
  250. return message;
  251. }
  252. *type = @"blob";
  253. return @{
  254. @"blobId": [self store:message],
  255. @"offset": @0,
  256. @"size": @(((NSData *)message).length),
  257. };
  258. }
  259. - (std::shared_ptr<facebook::react::TurboModule>)
  260. getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
  261. nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
  262. perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
  263. {
  264. return std::make_shared<facebook::react::NativeBlobModuleSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
  265. }
  266. @end
  267. Class RCTBlobManagerCls(void) {
  268. return RCTBlobManager.class;
  269. }