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