2019-10-16 20:03:47 +03:00
|
|
|
/*
|
2018-09-12 01:27:47 +03:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2018-01-26 20:06:14 +03:00
|
|
|
*
|
2018-02-17 05:24:55 +03:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2018-01-26 20:06:14 +03:00
|
|
|
*/
|
|
|
|
|
CocoaPods frameworks compatibility: Step 2 (#25619)
Summary:
This is my proposal for fixing `use_frameworks!` compatibility without breaking all `<React/*>` imports I outlined in https://github.com/facebook/react-native/pull/25393#issuecomment-508457700. If accepted, it will fix https://github.com/facebook/react-native/issues/25349.
It builds on the changes I made in https://github.com/facebook/react-native/pull/25496 by ensuring each podspec has a unique value for `header_dir` so that framework imports do not conflict. Every podspec which should be included in the `<React/*>` namespace now includes it's headers from `React-Core.podspec`.
The following pods can still be imported with `<React/*>` and so should not have breaking changes: `React-ART`,`React-DevSupport`, `React-CoreModules`, `React-RCTActionSheet`, `React-RCTAnimation`, `React-RCTBlob`, `React-RCTImage`, `React-RCTLinking`, `React-RCTNetwork`, `React-RCTPushNotification`, `React-RCTSettings`, `React-RCTText`, `React-RCTSettings`, `React-RCTVibration`, `React-RCTWebSocket` .
There are still a few breaking changes which I hope will be acceptable:
- `React-Core.podspec` has been moved to the root of the project. Any `Podfile` that references it will need to update the path.
- ~~`React-turbomodule-core`'s headers now live under `<turbomodule/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823.
- ~~`React-turbomodulesamples`'s headers now live under `<turbomodulesamples/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823.
- ~~`React-TypeSaferty`'s headers now live under `<TypeSafety/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511040967.
- ~~`React-jscallinvoker`'s headers now live under `<jscallinvoker/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823.
- Each podspec now uses `s.static_framework = true`. This means that a minimum of CocoaPods 1.5 ([released in April 2018](http://blog.cocoapods.org/CocoaPods-1.5.0/)) is now required. This is needed so that the ` __has_include` conditions can still work when frameworks are enabled.
Still to do:
- ~~Including `React-turbomodule-core` with `use_frameworks!` enabled causes the C++ import failures we saw in https://github.com/facebook/react-native/issues/25349. I'm sure it will be possible to fix this but I need to dig deeper (perhaps a custom modulemap would be needed).~~ Addressed by https://github.com/facebook/react-native/pull/25619/commits/33573511f02f3502a28bad48e085e9a4b8608302.
- I haven't got Fabric working yet. I wonder if it would be acceptable to move Fabric out of the `<React/*>` namespace since it is new? �
## Changelog
[iOS] [Fixed] - Fixed compatibility with CocoaPods frameworks.
Pull Request resolved: https://github.com/facebook/react-native/pull/25619
Test Plan:
### FB
```
buck build catalyst
```
### Sample Project
Everything should work exactly as before, where `use_frameworks!` is not in `Podfile`s. I have a branch on my [sample project](https://github.com/jtreanor/react-native-cocoapods-frameworks) here which has `use_frameworks!` in its `Podfile` to demonstrate this is fixed.
You can see that it works with these steps:
1. `git clone git@github.com:jtreanor/react-native-cocoapods-frameworks.git`
2. `git checkout fix-frameworks-subspecs`
3. `cd ios && pod install`
4. `cd .. && react-native run-ios`
The sample app will build and run successfully. To see that it still works without frameworks, remove `use_frameworks!` from the `Podfile` and do steps 3 and 4 again.
### RNTesterPods
`RNTesterPodsPods` can now work with or without `use_frameworks!`.
1. Go to the `RNTester` directory and run `pod install`.
2. Run the tests in `RNTesterPods.xcworkspace` to see that everything still works fine.
3. Uncomment the `use_frameworks!` line at the top of `RNTester/Podfile` and run `pod install` again.
4. Run the tests again and see that it still works with frameworks enabled.
Reviewed By: PeteTheHeat
Differential Revision: D16465247
Pulled By: PeteTheHeat
fbshipit-source-id: cad837e9cced06d30cc5b372af1c65c7780b9e7a
2019-07-25 08:26:42 +03:00
|
|
|
#import <React/RCTBlobManager.h>
|
2018-01-26 20:06:14 +03:00
|
|
|
|
|
|
|
#import <mutex>
|
|
|
|
|
2019-10-01 21:07:08 +03:00
|
|
|
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
2018-01-26 20:06:14 +03:00
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import <React/RCTNetworking.h>
|
2018-02-09 06:25:42 +03:00
|
|
|
#import <React/RCTUtils.h>
|
2018-01-26 20:06:14 +03:00
|
|
|
#import <React/RCTWebSocketModule.h>
|
2019-10-01 21:07:08 +03:00
|
|
|
|
|
|
|
#import "RCTBlobPlugins.h"
|
2019-05-09 00:10:14 +03:00
|
|
|
#import "RCTBlobCollector.h"
|
2018-01-26 20:06:14 +03:00
|
|
|
|
|
|
|
static NSString *const kBlobURIScheme = @"blob";
|
|
|
|
|
2019-10-01 21:07:08 +03:00
|
|
|
@interface RCTBlobManager () <RCTNetworkingRequestHandler, RCTNetworkingResponseHandler, RCTWebSocketContentHandler, NativeBlobModuleSpec>
|
2018-01-26 20:06:14 +03:00
|
|
|
|
|
|
|
@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;
|
2019-05-09 00:10:14 +03:00
|
|
|
@synthesize methodQueue = _methodQueue;
|
2018-01-26 20:06:14 +03:00
|
|
|
|
|
|
|
- (void)setBridge:(RCTBridge *)bridge
|
|
|
|
{
|
|
|
|
_bridge = bridge;
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(_blobsMutex);
|
|
|
|
_blobs = [NSMutableDictionary new];
|
2019-05-09 00:10:14 +03:00
|
|
|
|
|
|
|
facebook::react::RCTBlobCollector::install(self);
|
2018-01-26 20:06:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (BOOL)requiresMainQueueSetup
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary<NSString *, id> *)constantsToExport
|
2019-02-05 04:41:36 +03:00
|
|
|
{
|
|
|
|
return [self getConstants];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary<NSString *, id> *)getConstants
|
2018-01-26 20:06:14 +03:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
dispatch_async(_bridge.networking.methodQueue, ^{
|
|
|
|
[self->_bridge.networking addRequestHandler:self];
|
|
|
|
[self->_bridge.networking addResponseHandler:self];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-10-01 21:07:08 +03:00
|
|
|
RCT_EXPORT_METHOD(addWebSocketHandler:(double)socketID)
|
2018-01-26 20:06:14 +03:00
|
|
|
{
|
|
|
|
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
2019-10-01 21:07:08 +03:00
|
|
|
[self->_bridge.webSocketModule setContentHandler:self forSocketID:[NSNumber numberWithDouble:socketID]];
|
2018-01-26 20:06:14 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-10-01 21:07:08 +03:00
|
|
|
RCT_EXPORT_METHOD(removeWebSocketHandler:(double)socketID)
|
2018-01-26 20:06:14 +03:00
|
|
|
{
|
|
|
|
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
2019-10-01 21:07:08 +03:00
|
|
|
[self->_bridge.webSocketModule setContentHandler:nil forSocketID:[NSNumber numberWithDouble:socketID]];
|
2018-01-26 20:06:14 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
|
2019-10-01 21:07:08 +03:00
|
|
|
RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(double)socketID)
|
2018-01-26 20:06:14 +03:00
|
|
|
{
|
|
|
|
dispatch_async(_bridge.webSocketModule.methodQueue, ^{
|
2019-10-01 21:07:08 +03:00
|
|
|
[self->_bridge.webSocketModule sendData:[self resolve:blob] forSocketID:[NSNumber numberWithDouble:socketID]];
|
2018-01-26 20:06:14 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2018-03-27 20:34:09 +03:00
|
|
|
// 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];
|
2018-01-26 20:06:14 +03:00
|
|
|
return @{
|
|
|
|
@"blobId": [self store:data],
|
|
|
|
@"offset": @0,
|
|
|
|
@"size": @(data.length),
|
2018-02-09 06:25:42 +03:00
|
|
|
@"name": RCTNullIfNil([response suggestedFilename]),
|
|
|
|
@"type": RCTNullIfNil([response MIMEType]),
|
2018-01-26 20:06:14 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#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),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-10-01 21:07:08 +03:00
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
|
|
|
{
|
|
|
|
return std::make_shared<facebook::react::NativeBlobModuleSpecJSI>(self, jsInvoker);
|
|
|
|
}
|
|
|
|
|
2018-01-26 20:06:14 +03:00
|
|
|
@end
|
2019-10-01 21:07:08 +03:00
|
|
|
|
|
|
|
Class RCTBlobManagerCls(void) {
|
|
|
|
return RCTBlobManager.class;
|
|
|
|
}
|