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