1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXDevSettingsDataSource.h" 4 5#import <React/RCTLog.h> 6#import <React/RCTUtils.h> 7 8// redefined from RCTDevMenu.mm 9NSString *const EXDevSettingsUserDefaultsKey = @"RCTDevMenu"; 10NSString *const EXDevSettingShakeToShowDevMenu = @"shakeToShow"; 11NSString *const EXDevSettingProfilingEnabled = @"profilingEnabled"; 12NSString *const EXDevSettingHotLoadingEnabled = @"hotLoadingEnabled"; 13NSString *const EXDevSettingLiveReloadEnabled = @"liveReloadEnabled"; 14NSString *const EXDevSettingIsInspectorShown = @"showInspector"; 15NSString *const EXDevSettingIsDebuggingRemotely = @"isDebuggingRemotely"; 16 17@interface EXDevSettingsDataSource () 18 19@property (nonatomic, strong) NSString *scopeKey; 20@property (nonatomic, readonly) NSSet *settingsDisabledInProduction; 21 22@end 23 24@implementation EXDevSettingsDataSource { 25 NSMutableDictionary *_settings; 26 NSUserDefaults *_userDefaults; 27 BOOL _isDevelopment; 28} 29 30- (instancetype)initWithDefaultValues:(NSDictionary *)defaultValues 31 forScopeKey:(NSString *)scopeKey 32 isDevelopment:(BOOL)isDevelopment 33{ 34 if (self = [super init]) { 35 _scopeKey = scopeKey; 36 _userDefaults = [NSUserDefaults standardUserDefaults]; 37 _isDevelopment = isDevelopment; 38 _settingsDisabledInProduction = [NSSet setWithArray:@[ 39 EXDevSettingShakeToShowDevMenu, 40 EXDevSettingProfilingEnabled, 41 EXDevSettingHotLoadingEnabled, 42 EXDevSettingLiveReloadEnabled, 43 EXDevSettingIsInspectorShown, 44 EXDevSettingIsDebuggingRemotely, 45 ]]; 46 if (defaultValues) { 47 [self _reloadWithDefaults:defaultValues]; 48 } 49 } 50 return self; 51} 52 53- (void)updateSettingWithValue:(id)value forKey:(NSString *)key 54{ 55 RCTAssert((key != nil), @"%@", [NSString stringWithFormat:@"%@: Tried to update nil key", [self class]]); 56 57 id currentValue = [self settingForKey:key]; 58 if (currentValue == value || [currentValue isEqual:value]) { 59 return; 60 } 61 if (value) { 62 _settings[key] = value; 63 } else { 64 [_settings removeObjectForKey:key]; 65 } 66 [_userDefaults setObject:_settings forKey:[self _userDefaultsKey]]; 67} 68 69- (id)settingForKey:(NSString *)key 70{ 71 // live reload is always disabled in react-native@>=0.61 due to fast refresh 72 // we can remove this when live reload is completely removed from the 73 // react-native runtime 74 if ([key isEqualToString:EXDevSettingLiveReloadEnabled]) { 75 return @NO; 76 } 77 78 // prohibit these settings if not serving the experience as a developer 79 if (!_isDevelopment && [_settingsDisabledInProduction containsObject:key]) { 80 return @NO; 81 } 82 return _settings[key]; 83} 84 85#pragma mark - internal 86 87- (void)_reloadWithDefaults:(NSDictionary *)defaultValues 88{ 89 NSString *defaultsKey = [self _userDefaultsKey]; 90 NSDictionary *existingSettings = [_userDefaults objectForKey:defaultsKey]; 91 _settings = existingSettings ? [existingSettings mutableCopy] : [NSMutableDictionary dictionary]; 92 for (NSString *key in [defaultValues keyEnumerator]) { 93 if (!_settings[key]) { 94 _settings[key] = defaultValues[key]; 95 } 96 } 97 [_userDefaults setObject:_settings forKey:defaultsKey]; 98} 99 100- (NSString *)_userDefaultsKey 101{ 102 if (_scopeKey) { 103 return [NSString stringWithFormat:@"%@/%@", _scopeKey, EXDevSettingsUserDefaultsKey]; 104 } else { 105 RCTLogWarn(@"Can't scope dev settings because bridge is not set"); 106 return EXDevSettingsUserDefaultsKey; 107 } 108} 109 110@end 111