123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- /*
- * 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/RCTBlobManager.h>
- #import <mutex>
- #import <FBReactNativeSpec/FBReactNativeSpec.h>
- #import <React/RCTConvert.h>
- #import <React/RCTNetworking.h>
- #import <React/RCTUtils.h>
- #import <React/RCTWebSocketModule.h>
- #import "RCTBlobPlugins.h"
- #import "RCTBlobCollector.h"
- static NSString *const kBlobURIScheme = @"blob";
- @interface RCTBlobManager () <RCTNetworkingRequestHandler, RCTNetworkingResponseHandler, RCTWebSocketContentHandler, NativeBlobModuleSpec>
- @end
- @implementation RCTBlobManager
- {
- // Blobs should be thread safe since they are used from the websocket and networking module,
- // make sure to use proper locking when accessing this.
- NSMutableDictionary<NSString *, NSData *> *_blobs;
- std::mutex _blobsMutex;
- NSOperationQueue *_queue;
- }
- RCT_EXPORT_MODULE(BlobModule)
- @synthesize bridge = _bridge;
- @synthesize methodQueue = _methodQueue;
- @synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate;
- - (void)setBridge:(RCTBridge *)bridge
- {
- _bridge = bridge;
- std::lock_guard<std::mutex> lock(_blobsMutex);
- _blobs = [NSMutableDictionary new];
- facebook::react::RCTBlobCollector::install(self);
- }
- + (BOOL)requiresMainQueueSetup
- {
- return NO;
- }
- - (NSDictionary<NSString *, id> *)constantsToExport
- {
- return [self getConstants];
- }
- - (NSDictionary<NSString *, id> *)getConstants
- {
- return @{
- @"BLOB_URI_SCHEME": kBlobURIScheme,
- @"BLOB_URI_HOST": [NSNull null],
- };
- }
- - (NSString *)store:(NSData *)data
- {
- NSString *blobId = [NSUUID UUID].UUIDString;
- [self store:data withId:blobId];
- return blobId;
- }
- - (void)store:(NSData *)data withId:(NSString *)blobId
- {
- std::lock_guard<std::mutex> lock(_blobsMutex);
- _blobs[blobId] = data;
- }
- - (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
- {
- NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
- NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
- NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
- return [self resolve:blobId
- offset:offset ? [offset integerValue] : 0
- size:size ? [size integerValue] : -1];
- }
- - (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
- {
- NSData *data;
- {
- std::lock_guard<std::mutex> lock(_blobsMutex);
- data = _blobs[blobId];
- }
- if (!data) {
- return nil;
- }
- if (offset != 0 || (size != -1 && size != data.length)) {
- data = [data subdataWithRange:NSMakeRange(offset, size)];
- }
- return data;
- }
- - (NSData *)resolveURL:(NSURL *)url
- {
- NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
- NSString *blobId = components.path;
- NSInteger offset = 0;
- NSInteger size = -1;
- if (components.queryItems) {
- for (NSURLQueryItem *queryItem in components.queryItems) {
- if ([queryItem.name isEqualToString:@"offset"]) {
- offset = [queryItem.value integerValue];
- }
- if ([queryItem.name isEqualToString:@"size"]) {
- size = [queryItem.value integerValue];
- }
- }
- }
- if (blobId) {
- return [self resolve:blobId offset:offset size:size];
- }
- return nil;
- }
- - (void)remove:(NSString *)blobId
- {
- std::lock_guard<std::mutex> lock(_blobsMutex);
- [_blobs removeObjectForKey:blobId];
- }
- RCT_EXPORT_METHOD(addNetworkingHandler)
- {
- RCTNetworking *const networking = _bridge ? _bridge.networking : [_turboModuleLookupDelegate moduleForName:"RCTNetworking"];
- // TODO(T63516227): Why can methodQueue be nil here?
- // We don't want to do anything when methodQueue is nil.
- if (!networking.methodQueue) {
- return;
- }
- dispatch_async(networking.methodQueue, ^{
- [networking addRequestHandler:self];
- [networking addResponseHandler:self];
- });
- }
- RCT_EXPORT_METHOD(addWebSocketHandler:(double)socketID)
- {
- dispatch_async(_bridge.webSocketModule.methodQueue, ^{
- [self->_bridge.webSocketModule setContentHandler:self forSocketID:[NSNumber numberWithDouble:socketID]];
- });
- }
- RCT_EXPORT_METHOD(removeWebSocketHandler:(double)socketID)
- {
- dispatch_async(_bridge.webSocketModule.methodQueue, ^{
- [self->_bridge.webSocketModule setContentHandler:nil forSocketID:[NSNumber numberWithDouble:socketID]];
- });
- }
- // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(double)socketID)
- {
- dispatch_async(_bridge.webSocketModule.methodQueue, ^{
- [self->_bridge.webSocketModule sendData:[self resolve:blob] forSocketID:[NSNumber numberWithDouble:socketID]];
- });
- }
- RCT_EXPORT_METHOD(createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId)
- {
- NSMutableData *data = [NSMutableData new];
- for (NSDictionary<NSString *, id> *part in parts) {
- NSString *type = [RCTConvert NSString:part[@"type"]];
- if ([type isEqualToString:@"blob"]) {
- NSData *partData = [self resolve:part[@"data"]];
- [data appendData:partData];
- } else if ([type isEqualToString:@"string"]) {
- NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
- [data appendData:partData];
- } else {
- [NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];
- }
- }
- [self store:data withId:blobId];
- }
- RCT_EXPORT_METHOD(release:(NSString *)blobId)
- {
- [self remove:blobId];
- }
- #pragma mark - RCTURLRequestHandler methods
- - (BOOL)canHandleRequest:(NSURLRequest *)request
- {
- return [request.URL.scheme caseInsensitiveCompare:kBlobURIScheme] == NSOrderedSame;
- }
- - (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
- {
- // Lazy setup
- if (!_queue) {
- _queue = [NSOperationQueue new];
- _queue.maxConcurrentOperationCount = 2;
- }
- __weak __typeof(self) weakSelf = self;
- __weak __block NSBlockOperation *weakOp;
- __block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
- __typeof(self) strongSelf = weakSelf;
- if (!strongSelf) {
- return;
- }
- NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
- MIMEType:nil
- expectedContentLength:-1
- textEncodingName:nil];
- [delegate URLRequest:weakOp didReceiveResponse:response];
- NSData *data = [strongSelf resolveURL:response.URL];
- NSError *error;
- if (data) {
- [delegate URLRequest:weakOp didReceiveData:data];
- } else {
- error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
- }
- [delegate URLRequest:weakOp didCompleteWithError:error];
- }];
- weakOp = op;
- [_queue addOperation:op];
- return op;
- }
- - (void)cancelRequest:(NSOperation *)op
- {
- [op cancel];
- }
- #pragma mark - RCTNetworkingRequestHandler methods
- // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- - (BOOL)canHandleNetworkingRequest:(NSDictionary *)data
- {
- return data[@"blob"] != nil;
- }
- // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- - (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data
- {
- // @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]];
- NSString *contentType = @"application/octet-stream";
- NSString *blobType = [RCTConvert NSString:blob[@"type"]];
- if (blobType != nil && blobType.length > 0) {
- contentType = blob[@"type"];
- }
- return @{@"body": [self resolve:blob], @"contentType": contentType};
- }
- - (BOOL)canHandleNetworkingResponse:(NSString *)responseType
- {
- return [responseType isEqualToString:@"blob"];
- }
- - (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data
- {
- // An empty body will have nil for data, in this case we need to return
- // an empty blob as per the XMLHttpRequest spec.
- data = data ?: [NSData new];
- return @{
- @"blobId": [self store:data],
- @"offset": @0,
- @"size": @(data.length),
- @"name": RCTNullIfNil([response suggestedFilename]),
- @"type": RCTNullIfNil([response MIMEType]),
- };
- }
- #pragma mark - RCTWebSocketContentHandler methods
- - (id)processWebsocketMessage:(id)message
- forSocketID:(NSNumber *)socketID
- withType:(NSString *__autoreleasing _Nonnull *)type
- {
- if (![message isKindOfClass:[NSData class]]) {
- *type = @"text";
- return message;
- }
- *type = @"blob";
- return @{
- @"blobId": [self store:message],
- @"offset": @0,
- @"size": @(((NSData *)message).length),
- };
- }
- - (std::shared_ptr<facebook::react::TurboModule>)
- getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
- nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
- perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
- {
- return std::make_shared<facebook::react::NativeBlobModuleSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
- }
- @end
- Class RCTBlobManagerCls(void) {
- return RCTBlobManager.class;
- }
|