1/* 2 * Copyright (c) Meta Platforms, Inc. and 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 8#import <ABI47_0_0React/ABI47_0_0RCTImageStoreManager.h> 9 10#import <atomic> 11#import <memory> 12 13#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h> 14#import <ImageIO/ImageIO.h> 15#import <MobileCoreServices/UTType.h> 16#import <ABI47_0_0React/ABI47_0_0RCTAssert.h> 17#import <ABI47_0_0React/ABI47_0_0RCTLog.h> 18#import <ABI47_0_0React/ABI47_0_0RCTUtils.h> 19#import <ABI47_0_0React/ABI47_0_0RCTImageUtils.h> 20 21#import "ABI47_0_0RCTImagePlugins.h" 22 23static NSString *const ABI47_0_0RCTImageStoreURLScheme = @"rct-image-store"; 24 25@interface ABI47_0_0RCTImageStoreManager() <ABI47_0_0NativeImageStoreIOSSpec> 26@end 27 28@implementation ABI47_0_0RCTImageStoreManager 29{ 30 NSMutableDictionary<NSString *, NSData *> *_store; 31 NSUInteger _id; 32} 33 34@synthesize methodQueue = _methodQueue; 35 36ABI47_0_0RCT_EXPORT_MODULE() 37 38- (float)handlerPriority 39{ 40 return 1; 41} 42 43- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)(void))block 44{ 45 dispatch_async(_methodQueue, ^{ 46 [self removeImageForTag:imageTag]; 47 if (block) { 48 block(); 49 } 50 }); 51} 52 53- (NSString *)_storeImageData:(NSData *)imageData 54{ 55 ABI47_0_0RCTAssertThread(_methodQueue, @"Must be called on ABI47_0_0RCTImageStoreManager thread"); 56 57 if (!_store) { 58 _store = [NSMutableDictionary new]; 59 _id = 0; 60 } 61 62 NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", ABI47_0_0RCTImageStoreURLScheme, _id++]; 63 _store[imageTag] = imageData; 64 return imageTag; 65} 66 67- (void)storeImageData:(NSData *)imageData withBlock:(void (^)(NSString *imageTag))block 68{ 69 ABI47_0_0RCTAssertParam(block); 70 dispatch_async(_methodQueue, ^{ 71 block([self _storeImageData:imageData]); 72 }); 73} 74 75- (void)getImageDataForTag:(NSString *)imageTag withBlock:(void (^)(NSData *imageData))block 76{ 77 ABI47_0_0RCTAssertParam(block); 78 dispatch_async(_methodQueue, ^{ 79 block(self->_store[imageTag]); 80 }); 81} 82 83- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block 84{ 85 ABI47_0_0RCTAssertParam(block); 86 dispatch_async(_methodQueue, ^{ 87 NSString *imageTag = [self _storeImageData:ABI47_0_0RCTGetImageData(image, 0.75)]; 88 dispatch_async(dispatch_get_main_queue(), ^{ 89 block(imageTag); 90 }); 91 }); 92} 93 94ABI47_0_0RCT_EXPORT_METHOD(removeImageForTag:(NSString *)imageTag) 95{ 96 [_store removeObjectForKey:imageTag]; 97} 98 99ABI47_0_0RCT_EXPORT_METHOD(hasImageForTag:(NSString *)imageTag 100 callback:(ABI47_0_0RCTResponseSenderBlock)callback) 101{ 102 callback(@[@(_store[imageTag] != nil)]); 103} 104 105// TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag:? 106ABI47_0_0RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag 107 successCallback:(ABI47_0_0RCTResponseSenderBlock)successCallback 108 errorCallback:(ABI47_0_0RCTResponseSenderBlock)errorCallback) 109{ 110 NSData *imageData = _store[imageTag]; 111 if (!imageData) { 112 errorCallback(@[ABI47_0_0RCTJSErrorFromNSError(ABI47_0_0RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]))]); 113 return; 114 } 115 // Dispatching to a background thread to perform base64 encoding 116 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 117 successCallback(@[[imageData base64EncodedStringWithOptions:0]]); 118 }); 119} 120 121ABI47_0_0RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String 122 successCallback:(ABI47_0_0RCTResponseSenderBlock)successCallback 123 errorCallback:(ABI47_0_0RCTResponseSenderBlock)errorCallback) 124 125{ 126 // Dispatching to a background thread to perform base64 decoding 127 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 128 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 129 if (imageData) { 130 dispatch_async(self->_methodQueue, ^{ 131 successCallback(@[[self _storeImageData:imageData]]); 132 }); 133 } else { 134 errorCallback(@[ABI47_0_0RCTJSErrorFromNSError(ABI47_0_0RCTErrorWithMessage(@"Failed to add image from base64String"))]); 135 } 136 }); 137} 138 139#pragma mark - ABI47_0_0RCTURLRequestHandler 140 141- (BOOL)canHandleRequest:(NSURLRequest *)request 142{ 143 return [request.URL.scheme caseInsensitiveCompare:ABI47_0_0RCTImageStoreURLScheme] == NSOrderedSame; 144} 145 146- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<ABI47_0_0RCTURLRequestDelegate>)delegate 147{ 148 __block auto cancelled = std::make_shared<std::atomic<bool>>(false); 149 void (^cancellationBlock)(void) = ^{ 150 cancelled->store(true); 151 }; 152 153 // Dispatch async to give caller time to cancel the request 154 dispatch_async(_methodQueue, ^{ 155 if (cancelled->load()) { 156 return; 157 } 158 159 NSString *imageTag = request.URL.absoluteString; 160 NSData *imageData = self->_store[imageTag]; 161 if (!imageData) { 162 NSError *error = ABI47_0_0RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]); 163 [delegate URLRequest:cancellationBlock didCompleteWithError:error]; 164 return; 165 } 166 167 CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); 168 if (!sourceRef) { 169 NSError *error = ABI47_0_0RCTErrorWithMessage([NSString stringWithFormat:@"Unable to decode data for imageTag: %@", imageTag]); 170 [delegate URLRequest:cancellationBlock didCompleteWithError:error]; 171 return; 172 } 173 CFStringRef UTI = CGImageSourceGetType(sourceRef); 174 CFRelease(sourceRef); 175 176 NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType); 177 NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL 178 MIMEType:MIMEType 179 expectedContentLength:imageData.length 180 textEncodingName:nil]; 181 CFRelease(UTI); 182 183 [delegate URLRequest:cancellationBlock didReceiveResponse:response]; 184 [delegate URLRequest:cancellationBlock didReceiveData:imageData]; 185 [delegate URLRequest:cancellationBlock didCompleteWithError:nil]; 186 187 }); 188 189 return cancellationBlock; 190} 191 192- (void)cancelRequest:(id)requestToken 193{ 194 if (requestToken) { 195 ((void (^)(void))requestToken)(); 196 } 197} 198 199- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:(const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params 200{ 201 return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeImageStoreIOSSpecJSI>(params); 202} 203 204@end 205 206@implementation ABI47_0_0RCTImageStoreManager (Deprecated) 207 208- (NSString *)storeImage:(UIImage *)image 209{ 210 ABI47_0_0RCTAssertMainQueue(); 211 ABI47_0_0RCTLogWarn(@"ABI47_0_0RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead."); 212 __block NSString *imageTag; 213 dispatch_sync(_methodQueue, ^{ 214 imageTag = [self _storeImageData:ABI47_0_0RCTGetImageData(image, 0.75)]; 215 }); 216 return imageTag; 217} 218 219- (UIImage *)imageForTag:(NSString *)imageTag 220{ 221 ABI47_0_0RCTAssertMainQueue(); 222 ABI47_0_0RCTLogWarn(@"ABI47_0_0RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead."); 223 __block NSData *imageData; 224 dispatch_sync(_methodQueue, ^{ 225 imageData = self->_store[imageTag]; 226 }); 227 return [UIImage imageWithData:imageData]; 228} 229 230- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block 231{ 232 ABI47_0_0RCTAssertParam(block); 233 dispatch_async(_methodQueue, ^{ 234 NSData *imageData = self->_store[imageTag]; 235 dispatch_async(dispatch_get_main_queue(), ^{ 236 // imageWithData: is not thread-safe, so we can't do this on methodQueue 237 block([UIImage imageWithData:imageData]); 238 }); 239 }); 240} 241 242@end 243 244@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTImageStoreManager) 245 246- (ABI47_0_0RCTImageStoreManager *)imageStoreManager 247{ 248 return [self moduleForClass:[ABI47_0_0RCTImageStoreManager class]]; 249} 250 251@end 252 253Class ABI47_0_0RCTImageStoreManagerCls(void) { 254 return ABI47_0_0RCTImageStoreManager.class; 255} 256