mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
2160377574
Summary: Refs: [0.62 release](https://reactnative.dev/blog/#moving-apple-tv-to-react-native-tvos), https://github.com/facebook/react-native/issues/28706, https://github.com/facebook/react-native/issues/28743, https://github.com/facebook/react-native/issues/29018 This PR removes most of the tvOS remnants in the code. Most of the changes are related to the tvOS platform removal from `.podspec` files, tvOS specific conditionals removal (Obj-C + JS) or tvOS CI/testing pipeline related code. In addition to the changes listed above I have removed the deprecated `Platform.isTVOS` method. I'm not sure how `Platform.isTV` method is correlated with Android TV devices support which is technically not deprecated in the core so I left this method untouched for now. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> * **[Internal] [Removed]** - remove most of tvOS remnants from the code: * `TVEventHandler`, `TVTouchable`, `RCTTVView`, `RCTTVRemoteHandler` and `RCTTVNavigationEventEmitter` * **[Internal] [Removed]** - remove `TARGET_TV_OS` flag and all the usages * **[iOS] [Removed]** - remove deprecated `Platform.isTVOS` method * **[iOS] [Removed]** - remove deprecated and TV related props from View: * `isTVSelectable`, `hasTVPreferredFocus` and `tvParallaxProperties` * **[iOS] [Removed]** - remove `BackHandler` utility implementation Pull Request resolved: https://github.com/facebook/react-native/pull/29407 Test Plan: Local tests (and iOS CI run) do not yield any errors, but I'm not sure how the CI pipeline would react to those changes. That is the reason why this PR is being posted as Draft. Some tweaks and code adjustment could be required. Reviewed By: PeteTheHeat Differential Revision: D22619441 Pulled By: shergin fbshipit-source-id: 9aaf3840c5e8bd469c2cfcfa7c5b441ef71b30b6
467 lines
14 KiB
Plaintext
467 lines
14 KiB
Plaintext
/*
|
|
* 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 "RCTAsyncLocalStorage.h"
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#import <CommonCrypto/CommonCryptor.h>
|
|
#import <CommonCrypto/CommonDigest.h>
|
|
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
|
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
#import "CoreModulesPlugins.h"
|
|
|
|
static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
|
|
static NSString *const RCTManifestFileName = @"manifest.json";
|
|
static const NSUInteger RCTInlineValueThreshold = 1024;
|
|
|
|
#pragma mark - Static helper functions
|
|
|
|
static NSDictionary *RCTErrorForKey(NSString *key)
|
|
{
|
|
if (![key isKindOfClass:[NSString class]]) {
|
|
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key" : key});
|
|
} else if (key.length < 1) {
|
|
return RCTMakeAndLogError(@"Invalid key - must be at least one character. Key: ", key, @{@"key" : key});
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> **errors)
|
|
{
|
|
if (error && errors) {
|
|
if (!*errors) {
|
|
*errors = [NSMutableArray new];
|
|
}
|
|
[*errors addObject:error];
|
|
}
|
|
}
|
|
|
|
static NSString *RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
|
|
{
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
|
NSError *error;
|
|
NSStringEncoding encoding;
|
|
NSString *entryString = [NSString stringWithContentsOfFile:filePath usedEncoding:&encoding error:&error];
|
|
NSDictionary *extraData = @{@"key" : RCTNullIfNil(key)};
|
|
|
|
if (error) {
|
|
if (errorOut)
|
|
*errorOut = RCTMakeError(@"Failed to read storage file.", error, extraData);
|
|
return nil;
|
|
}
|
|
|
|
if (encoding != NSUTF8StringEncoding) {
|
|
if (errorOut)
|
|
*errorOut = RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), extraData);
|
|
return nil;
|
|
}
|
|
return entryString;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static NSString *RCTGetStorageDirectory()
|
|
{
|
|
static NSString *storageDirectory = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
storageDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
|
|
storageDirectory = [storageDirectory stringByAppendingPathComponent:RCTStorageDirectory];
|
|
});
|
|
return storageDirectory;
|
|
}
|
|
|
|
static NSString *RCTGetManifestFilePath()
|
|
{
|
|
static NSString *manifestFilePath = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName];
|
|
});
|
|
return manifestFilePath;
|
|
}
|
|
|
|
// Only merges objects - all other types are just clobbered (including arrays)
|
|
static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
|
|
{
|
|
BOOL modified = NO;
|
|
for (NSString *key in source) {
|
|
id sourceValue = source[key];
|
|
id destinationValue = destination[key];
|
|
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
|
|
if ([destinationValue isKindOfClass:[NSDictionary class]]) {
|
|
if ([destinationValue classForCoder] != [NSMutableDictionary class]) {
|
|
destinationValue = [destinationValue mutableCopy];
|
|
}
|
|
if (RCTMergeRecursive(destinationValue, sourceValue)) {
|
|
destination[key] = destinationValue;
|
|
modified = YES;
|
|
}
|
|
} else {
|
|
destination[key] = [sourceValue copy];
|
|
modified = YES;
|
|
}
|
|
} else if (![source isEqual:destinationValue]) {
|
|
destination[key] = [sourceValue copy];
|
|
modified = YES;
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
static dispatch_queue_t RCTGetMethodQueue()
|
|
{
|
|
// We want all instances to share the same queue since they will be reading/writing the same files.
|
|
static dispatch_queue_t queue;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
queue = dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
|
|
});
|
|
return queue;
|
|
}
|
|
|
|
static NSCache *RCTGetCache()
|
|
{
|
|
// We want all instances to share the same cache since they will be reading/writing the same files.
|
|
static NSCache *cache;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
cache = [NSCache new];
|
|
cache.totalCostLimit = 2 * 1024 * 1024; // 2MB
|
|
|
|
// Clear cache in the event of a memory warning
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
|
|
object:nil
|
|
queue:nil
|
|
usingBlock:^(__unused NSNotification *note) {
|
|
[cache removeAllObjects];
|
|
}];
|
|
});
|
|
return cache;
|
|
}
|
|
|
|
static BOOL RCTHasCreatedStorageDirectory = NO;
|
|
static NSDictionary *RCTDeleteStorageDirectory()
|
|
{
|
|
NSError *error;
|
|
[[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error];
|
|
RCTHasCreatedStorageDirectory = NO;
|
|
return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil;
|
|
}
|
|
|
|
#pragma mark - RCTAsyncLocalStorage
|
|
|
|
@interface RCTAsyncLocalStorage () <NativeAsyncStorageSpec>
|
|
@end
|
|
|
|
@implementation RCTAsyncLocalStorage {
|
|
BOOL _haveSetup;
|
|
// The manifest is a dictionary of all keys with small values inlined. Null values indicate values that are stored
|
|
// in separate files (as opposed to nil values which don't exist). The manifest is read off disk at startup, and
|
|
// written to disk after all mutations.
|
|
NSMutableDictionary<NSString *, NSString *> *_manifest;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return RCTGetMethodQueue();
|
|
}
|
|
|
|
- (void)clearAllData
|
|
{
|
|
dispatch_async(RCTGetMethodQueue(), ^{
|
|
[self->_manifest removeAllObjects];
|
|
[RCTGetCache() removeAllObjects];
|
|
RCTDeleteStorageDirectory();
|
|
});
|
|
}
|
|
|
|
+ (void)clearAllData
|
|
{
|
|
dispatch_async(RCTGetMethodQueue(), ^{
|
|
[RCTGetCache() removeAllObjects];
|
|
RCTDeleteStorageDirectory();
|
|
});
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
if (_clearOnInvalidate) {
|
|
[RCTGetCache() removeAllObjects];
|
|
RCTDeleteStorageDirectory();
|
|
}
|
|
_clearOnInvalidate = NO;
|
|
[_manifest removeAllObjects];
|
|
_haveSetup = NO;
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return _haveSetup;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self invalidate];
|
|
}
|
|
|
|
- (NSString *)_filePathForKey:(NSString *)key
|
|
{
|
|
NSString *safeFileName = RCTMD5Hash(key);
|
|
return [RCTGetStorageDirectory() stringByAppendingPathComponent:safeFileName];
|
|
}
|
|
|
|
- (NSDictionary *)_ensureSetup
|
|
{
|
|
RCTAssertThread(RCTGetMethodQueue(), @"Must be executed on storage thread");
|
|
|
|
NSError *error = nil;
|
|
if (!RCTHasCreatedStorageDirectory) {
|
|
[[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory()
|
|
withIntermediateDirectories:YES
|
|
attributes:nil
|
|
error:&error];
|
|
if (error) {
|
|
return RCTMakeError(@"Failed to create storage directory.", error, nil);
|
|
}
|
|
RCTHasCreatedStorageDirectory = YES;
|
|
}
|
|
if (!_haveSetup) {
|
|
NSDictionary *errorOut;
|
|
NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), RCTManifestFileName, &errorOut);
|
|
_manifest = serialized ? RCTJSONParseMutable(serialized, &error) : [NSMutableDictionary new];
|
|
if (error) {
|
|
RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error);
|
|
_manifest = [NSMutableDictionary new];
|
|
}
|
|
_haveSetup = YES;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSDictionary *)_writeManifest:(NSMutableArray<NSDictionary *> **)errors
|
|
{
|
|
NSError *error;
|
|
NSString *serialized = RCTJSONStringify(_manifest, &error);
|
|
[serialized writeToFile:RCTGetManifestFilePath() atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
|
NSDictionary *errorOut;
|
|
if (error) {
|
|
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
|
|
RCTAppendError(errorOut, errors);
|
|
}
|
|
return errorOut;
|
|
}
|
|
|
|
- (NSDictionary *)_appendItemForKey:(NSString *)key toArray:(NSMutableArray<NSArray<NSString *> *> *)result
|
|
{
|
|
NSDictionary *errorOut = RCTErrorForKey(key);
|
|
if (errorOut) {
|
|
return errorOut;
|
|
}
|
|
NSString *value = [self _getValueForKey:key errorOut:&errorOut];
|
|
[result addObject:@[ key, RCTNullIfNil(value) ]]; // Insert null if missing or failure.
|
|
return errorOut;
|
|
}
|
|
|
|
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
|
|
{
|
|
NSString *value = _manifest[key]; // nil means missing, null means there may be a data file, else: NSString
|
|
if (value == (id)kCFNull) {
|
|
value = [RCTGetCache() objectForKey:key];
|
|
if (!value) {
|
|
NSString *filePath = [self _filePathForKey:key];
|
|
value = RCTReadFile(filePath, key, errorOut);
|
|
if (value) {
|
|
[RCTGetCache() setObject:value forKey:key cost:value.length];
|
|
} else {
|
|
// file does not exist after all, so remove from manifest (no need to save
|
|
// manifest immediately though, as cost of checking again next time is negligible)
|
|
[_manifest removeObjectForKey:key];
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
- (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL *)changedManifest
|
|
{
|
|
if (entry.count != 2) {
|
|
return RCTMakeAndLogError(@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
|
|
}
|
|
NSString *key = entry[0];
|
|
NSDictionary *errorOut = RCTErrorForKey(key);
|
|
if (errorOut) {
|
|
return errorOut;
|
|
}
|
|
NSString *value = entry[1];
|
|
NSString *filePath = [self _filePathForKey:key];
|
|
NSError *error;
|
|
if (value.length <= RCTInlineValueThreshold) {
|
|
if (_manifest[key] == (id)kCFNull) {
|
|
// If the value already existed but wasn't inlined, remove the old file.
|
|
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
|
[RCTGetCache() removeObjectForKey:key];
|
|
}
|
|
*changedManifest = YES;
|
|
_manifest[key] = value;
|
|
return nil;
|
|
}
|
|
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
|
[RCTGetCache() setObject:value forKey:key cost:value.length];
|
|
if (error) {
|
|
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key" : key});
|
|
} else if (_manifest[key] != (id)kCFNull) {
|
|
*changedManifest = YES;
|
|
_manifest[key] = (id)kCFNull;
|
|
}
|
|
return errorOut;
|
|
}
|
|
|
|
#pragma mark - Exported JS Functions
|
|
|
|
RCT_EXPORT_METHOD(multiGet : (NSArray<NSString *> *)keys callback : (RCTResponseSenderBlock)callback)
|
|
{
|
|
NSDictionary *errorOut = [self _ensureSetup];
|
|
if (errorOut) {
|
|
callback(@[ @[ errorOut ], (id)kCFNull ]);
|
|
return;
|
|
}
|
|
NSMutableArray<NSDictionary *> *errors;
|
|
NSMutableArray<NSArray<NSString *> *> *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
|
|
for (NSString *key in keys) {
|
|
id keyError;
|
|
id value = [self _getValueForKey:key errorOut:&keyError];
|
|
[result addObject:@[ key, RCTNullIfNil(value) ]];
|
|
RCTAppendError(keyError, &errors);
|
|
}
|
|
callback(@[ RCTNullIfNil(errors), result ]);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(multiSet : (NSArray<NSArray<NSString *> *> *)kvPairs callback : (RCTResponseSenderBlock)callback)
|
|
{
|
|
NSDictionary *errorOut = [self _ensureSetup];
|
|
if (errorOut) {
|
|
callback(@[ @[ errorOut ] ]);
|
|
return;
|
|
}
|
|
BOOL changedManifest = NO;
|
|
NSMutableArray<NSDictionary *> *errors;
|
|
for (NSArray<NSString *> *entry in kvPairs) {
|
|
NSDictionary *keyError = [self _writeEntry:entry changedManifest:&changedManifest];
|
|
RCTAppendError(keyError, &errors);
|
|
}
|
|
if (changedManifest) {
|
|
[self _writeManifest:&errors];
|
|
}
|
|
callback(@[ RCTNullIfNil(errors) ]);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(multiMerge : (NSArray<NSArray<NSString *> *> *)kvPairs callback : (RCTResponseSenderBlock)callback)
|
|
{
|
|
NSDictionary *errorOut = [self _ensureSetup];
|
|
if (errorOut) {
|
|
callback(@[ @[ errorOut ] ]);
|
|
return;
|
|
}
|
|
BOOL changedManifest = NO;
|
|
NSMutableArray<NSDictionary *> *errors;
|
|
for (__strong NSArray<NSString *> *entry in kvPairs) {
|
|
NSDictionary *keyError;
|
|
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
|
|
if (!keyError) {
|
|
if (value) {
|
|
NSError *jsonError;
|
|
NSMutableDictionary *mergedVal = RCTJSONParseMutable(value, &jsonError);
|
|
if (RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &jsonError))) {
|
|
entry = @[ entry[0], RCTNullIfNil(RCTJSONStringify(mergedVal, NULL)) ];
|
|
}
|
|
if (jsonError) {
|
|
keyError = RCTJSErrorFromNSError(jsonError);
|
|
}
|
|
}
|
|
if (!keyError) {
|
|
keyError = [self _writeEntry:entry changedManifest:&changedManifest];
|
|
}
|
|
}
|
|
RCTAppendError(keyError, &errors);
|
|
}
|
|
if (changedManifest) {
|
|
[self _writeManifest:&errors];
|
|
}
|
|
callback(@[ RCTNullIfNil(errors) ]);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(multiRemove : (NSArray<NSString *> *)keys callback : (RCTResponseSenderBlock)callback)
|
|
{
|
|
NSDictionary *errorOut = [self _ensureSetup];
|
|
if (errorOut) {
|
|
callback(@[ @[ errorOut ] ]);
|
|
return;
|
|
}
|
|
NSMutableArray<NSDictionary *> *errors;
|
|
BOOL changedManifest = NO;
|
|
for (NSString *key in keys) {
|
|
NSDictionary *keyError = RCTErrorForKey(key);
|
|
if (!keyError) {
|
|
if (_manifest[key] == (id)kCFNull) {
|
|
NSString *filePath = [self _filePathForKey:key];
|
|
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
|
[RCTGetCache() removeObjectForKey:key];
|
|
}
|
|
if (_manifest[key]) {
|
|
changedManifest = YES;
|
|
[_manifest removeObjectForKey:key];
|
|
}
|
|
}
|
|
RCTAppendError(keyError, &errors);
|
|
}
|
|
if (changedManifest) {
|
|
[self _writeManifest:&errors];
|
|
}
|
|
callback(@[ RCTNullIfNil(errors) ]);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(clear : (RCTResponseSenderBlock)callback)
|
|
{
|
|
[_manifest removeAllObjects];
|
|
[RCTGetCache() removeAllObjects];
|
|
NSDictionary *error = RCTDeleteStorageDirectory();
|
|
callback(@[ RCTNullIfNil(error) ]);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(getAllKeys : (RCTResponseSenderBlock)callback)
|
|
{
|
|
NSDictionary *errorOut = [self _ensureSetup];
|
|
if (errorOut) {
|
|
callback(@[ errorOut, (id)kCFNull ]);
|
|
} else {
|
|
callback(@[ (id)kCFNull, _manifest.allKeys ]);
|
|
}
|
|
}
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
{
|
|
return std::make_shared<facebook::react::NativeAsyncStorageSpecJSI>(params);
|
|
}
|
|
|
|
@end
|
|
|
|
Class RCTAsyncLocalStorageCls(void)
|
|
{
|
|
return RCTAsyncLocalStorage.class;
|
|
}
|