This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
572 lines
17 KiB
572 lines
17 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
|
|
#import "RCTDevSettings.h" |
|
|
|
#import <objc/runtime.h> |
|
|
|
#import <JavaScriptCore/JavaScriptCore.h> |
|
|
|
#import <jschelpers/JavaScriptCore.h> |
|
|
|
#import "RCTBridge+Private.h" |
|
#import "RCTBridgeModule.h" |
|
#import "RCTEventDispatcher.h" |
|
#import "RCTJSCSamplingProfiler.h" |
|
#import "RCTLog.h" |
|
#import "RCTPackagerClient.h" |
|
#import "RCTProfile.h" |
|
#import "RCTUtils.h" |
|
|
|
static NSString *const kRCTDevSettingProfilingEnabled = @"profilingEnabled"; |
|
static NSString *const kRCTDevSettingHotLoadingEnabled = @"hotLoadingEnabled"; |
|
static NSString *const kRCTDevSettingLiveReloadEnabled = @"liveReloadEnabled"; |
|
static NSString *const kRCTDevSettingIsInspectorShown = @"showInspector"; |
|
static NSString *const kRCTDevSettingIsDebuggingRemotely = @"isDebuggingRemotely"; |
|
static NSString *const kRCTDevSettingExecutorOverrideClass = @"executor-override"; |
|
static NSString *const kRCTDevSettingShakeToShowDevMenu = @"shakeToShow"; |
|
static NSString *const kRCTDevSettingIsPerfMonitorShown = @"RCTPerfMonitorKey"; |
|
static NSString *const kRCTDevSettingStartSamplingProfilerOnLaunch = @"startSamplingProfilerOnLaunch"; |
|
|
|
static NSString *const kRCTDevSettingsUserDefaultsKey = @"RCTDevMenu"; |
|
|
|
#if ENABLE_PACKAGER_CONNECTION |
|
#import "RCTPackagerConnection.h" |
|
#endif |
|
|
|
#if RCT_ENABLE_INSPECTOR |
|
#import "RCTInspectorDevServerHelper.h" |
|
#import <jschelpers/JSCWrapper.h> |
|
#endif |
|
|
|
#if RCT_DEV |
|
|
|
@interface RCTDevSettingsUserDefaultsDataSource : NSObject <RCTDevSettingsDataSource> |
|
|
|
@end |
|
|
|
@implementation RCTDevSettingsUserDefaultsDataSource { |
|
NSMutableDictionary *_settings; |
|
NSUserDefaults *_userDefaults; |
|
} |
|
|
|
- (instancetype)init |
|
{ |
|
return [self initWithDefaultValues:nil]; |
|
} |
|
|
|
- (instancetype)initWithDefaultValues:(NSDictionary *)defaultValues |
|
{ |
|
if (self = [super init]) { |
|
_userDefaults = [NSUserDefaults standardUserDefaults]; |
|
if (defaultValues) { |
|
[self _reloadWithDefaults:defaultValues]; |
|
} |
|
} |
|
return self; |
|
} |
|
|
|
- (void)updateSettingWithValue:(id)value forKey:(NSString *)key |
|
{ |
|
RCTAssert((key != nil), @"%@", [NSString stringWithFormat:@"%@: Tried to update nil key", [self class]]); |
|
|
|
id currentValue = [self settingForKey:key]; |
|
if (currentValue == value || [currentValue isEqual:value]) { |
|
return; |
|
} |
|
if (value) { |
|
_settings[key] = value; |
|
} else { |
|
[_settings removeObjectForKey:key]; |
|
} |
|
[_userDefaults setObject:_settings forKey:kRCTDevSettingsUserDefaultsKey]; |
|
} |
|
|
|
- (id)settingForKey:(NSString *)key |
|
{ |
|
return _settings[key]; |
|
} |
|
|
|
- (void)_reloadWithDefaults:(NSDictionary *)defaultValues |
|
{ |
|
NSDictionary *existingSettings = [_userDefaults objectForKey:kRCTDevSettingsUserDefaultsKey]; |
|
_settings = existingSettings ? [existingSettings mutableCopy] : [NSMutableDictionary dictionary]; |
|
for (NSString *key in [defaultValues keyEnumerator]) { |
|
if (!_settings[key]) { |
|
_settings[key] = defaultValues[key]; |
|
} |
|
} |
|
[_userDefaults setObject:_settings forKey:kRCTDevSettingsUserDefaultsKey]; |
|
} |
|
|
|
@end |
|
|
|
@interface RCTDevSettings () <RCTBridgeModule, RCTInvalidating> |
|
{ |
|
NSURLSessionDataTask *_liveReloadUpdateTask; |
|
NSURL *_liveReloadURL; |
|
BOOL _isJSLoaded; |
|
#if ENABLE_PACKAGER_CONNECTION |
|
RCTHandlerToken _reloadToken; |
|
RCTHandlerToken _pokeSamplingProfilerToken; |
|
#endif |
|
} |
|
|
|
@property (nonatomic, strong) Class executorClass; |
|
@property (nonatomic, readwrite, strong) id<RCTDevSettingsDataSource> dataSource; |
|
|
|
@end |
|
|
|
@implementation RCTDevSettings |
|
|
|
@synthesize bridge = _bridge; |
|
|
|
RCT_EXPORT_MODULE() |
|
|
|
+ (BOOL)requiresMainQueueSetup |
|
{ |
|
return YES; // RCT_DEV-only |
|
} |
|
|
|
- (instancetype)init |
|
{ |
|
// default behavior is to use NSUserDefaults |
|
NSDictionary *defaultValues = @{ |
|
kRCTDevSettingShakeToShowDevMenu: @YES, |
|
}; |
|
RCTDevSettingsUserDefaultsDataSource *dataSource = [[RCTDevSettingsUserDefaultsDataSource alloc] initWithDefaultValues:defaultValues]; |
|
return [self initWithDataSource:dataSource]; |
|
} |
|
|
|
- (instancetype)initWithDataSource:(id<RCTDevSettingsDataSource>)dataSource |
|
{ |
|
if (self = [super init]) { |
|
_dataSource = dataSource; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(jsLoaded:) |
|
name:RCTJavaScriptDidLoadNotification |
|
object:nil]; |
|
|
|
// Delay setup until after Bridge init |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[self _synchronizeAllSettings]; |
|
}); |
|
} |
|
return self; |
|
} |
|
|
|
- (void)setBridge:(RCTBridge *)bridge |
|
{ |
|
RCTAssert(_bridge == nil, @"RCTDevSettings module should not be reused"); |
|
_bridge = bridge; |
|
|
|
#if ENABLE_PACKAGER_CONNECTION |
|
RCTBridge *__weak weakBridge = bridge; |
|
_reloadToken = |
|
[[RCTPackagerConnection sharedPackagerConnection] |
|
addNotificationHandler:^(id params) { |
|
if (params != (id)kCFNull && [params[@"debug"] boolValue]) { |
|
weakBridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor"); |
|
} |
|
[weakBridge reload]; |
|
} |
|
queue:dispatch_get_main_queue() |
|
forMethod:@"reload"]; |
|
|
|
_pokeSamplingProfilerToken = |
|
[[RCTPackagerConnection sharedPackagerConnection] |
|
addRequestHandler:^(NSDictionary<NSString *, id> *params, RCTPackagerClientResponder *responder) { |
|
pokeSamplingProfiler(weakBridge, responder); |
|
} |
|
queue:dispatch_get_main_queue() |
|
forMethod:@"pokeSamplingProfiler"]; |
|
#endif |
|
|
|
#if RCT_ENABLE_INSPECTOR |
|
// we need this dispatch back to the main thread because even though this |
|
// is executed on the main thread, at this point the bridge is not yet |
|
// finished with its initialisation. But it does finish by the time it |
|
// relinquishes control of the main thread, so only queue on the JS thread |
|
// after the current main thread operation is done. |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[bridge dispatchBlock:^{ |
|
[RCTInspectorDevServerHelper connectWithBundleURL:bridge.bundleURL]; |
|
} queue:RCTJSThread]; |
|
}); |
|
#endif |
|
} |
|
|
|
static void pokeSamplingProfiler(RCTBridge *const bridge, RCTPackagerClientResponder *const responder) |
|
{ |
|
if (!bridge) { |
|
[responder respondWithError:@"The bridge is nil. Try again."]; |
|
return; |
|
} |
|
|
|
JSGlobalContextRef globalContext = bridge.jsContextRef; |
|
if (!JSC_JSSamplingProfilerEnabled(globalContext)) { |
|
[responder respondWithError:@"The JSSamplingProfiler is disabled. See 'iOS specific setup' section here https://fburl.com/u4lw7xeq for some help"]; |
|
return; |
|
} |
|
|
|
// JSPokeSamplingProfiler() toggles the profiling process |
|
JSValueRef jsResult = JSC_JSPokeSamplingProfiler(globalContext); |
|
if (JSC_JSValueGetType(globalContext, jsResult) == kJSTypeNull) { |
|
[responder respondWithResult:@"started"]; |
|
} else { |
|
JSContext *context = [JSC_JSContext(globalContext) contextWithJSGlobalContextRef:globalContext]; |
|
NSString *results = [[JSC_JSValue(globalContext) valueWithJSValueRef:jsResult inContext:context] toObject]; |
|
[responder respondWithResult:results]; |
|
} |
|
} |
|
|
|
- (dispatch_queue_t)methodQueue |
|
{ |
|
return dispatch_get_main_queue(); |
|
} |
|
|
|
- (void)invalidate |
|
{ |
|
[_liveReloadUpdateTask cancel]; |
|
#if ENABLE_PACKAGER_CONNECTION |
|
[[RCTPackagerConnection sharedPackagerConnection] removeHandler:_reloadToken]; |
|
[[RCTPackagerConnection sharedPackagerConnection] removeHandler:_pokeSamplingProfilerToken]; |
|
#endif |
|
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
} |
|
|
|
- (void)_updateSettingWithValue:(id)value forKey:(NSString *)key |
|
{ |
|
[_dataSource updateSettingWithValue:value forKey:key]; |
|
} |
|
|
|
- (id)settingForKey:(NSString *)key |
|
{ |
|
return [_dataSource settingForKey:key]; |
|
} |
|
|
|
- (BOOL)isNuclideDebuggingAvailable |
|
{ |
|
#if RCT_ENABLE_INSPECTOR |
|
return _bridge.isInspectable; |
|
#else |
|
return false; |
|
#endif // RCT_ENABLE_INSPECTOR |
|
} |
|
|
|
- (BOOL)isRemoteDebuggingAvailable |
|
{ |
|
Class jsDebuggingExecutorClass = objc_lookUpClass("RCTWebSocketExecutor"); |
|
return (jsDebuggingExecutorClass != nil); |
|
} |
|
|
|
- (BOOL)isHotLoadingAvailable |
|
{ |
|
return _bridge.bundleURL && !_bridge.bundleURL.fileURL; // Only works when running from server |
|
} |
|
|
|
- (BOOL)isLiveReloadAvailable |
|
{ |
|
return (_liveReloadURL != nil); |
|
} |
|
|
|
- (BOOL)isJSCSamplingProfilerAvailable |
|
{ |
|
return JSC_JSSamplingProfilerEnabled(_bridge.jsContextRef); |
|
} |
|
|
|
RCT_EXPORT_METHOD(reload) |
|
{ |
|
[_bridge reload]; |
|
} |
|
|
|
RCT_EXPORT_METHOD(setIsShakeToShowDevMenuEnabled:(BOOL)enabled) |
|
{ |
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingShakeToShowDevMenu]; |
|
} |
|
|
|
- (BOOL)isShakeToShowDevMenuEnabled |
|
{ |
|
return [[self settingForKey:kRCTDevSettingShakeToShowDevMenu] boolValue]; |
|
} |
|
|
|
RCT_EXPORT_METHOD(setIsDebuggingRemotely:(BOOL)enabled) |
|
{ |
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingIsDebuggingRemotely]; |
|
[self _remoteDebugSettingDidChange]; |
|
} |
|
|
|
- (BOOL)isDebuggingRemotely |
|
{ |
|
return [[self settingForKey:kRCTDevSettingIsDebuggingRemotely] boolValue]; |
|
} |
|
|
|
- (void)_remoteDebugSettingDidChange |
|
{ |
|
// This value is passed as a command-line argument, so fall back to reading from NSUserDefaults directly |
|
NSString *executorOverride = [[NSUserDefaults standardUserDefaults] stringForKey:kRCTDevSettingExecutorOverrideClass]; |
|
Class executorOverrideClass = executorOverride ? NSClassFromString(executorOverride) : nil; |
|
if (executorOverrideClass) { |
|
self.executorClass = executorOverrideClass; |
|
} else { |
|
BOOL enabled = self.isRemoteDebuggingAvailable && self.isDebuggingRemotely; |
|
self.executorClass = enabled ? objc_getClass("RCTWebSocketExecutor") : nil; |
|
} |
|
} |
|
|
|
RCT_EXPORT_METHOD(setProfilingEnabled:(BOOL)enabled) |
|
{ |
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingProfilingEnabled]; |
|
[self _profilingSettingDidChange]; |
|
} |
|
|
|
- (BOOL)isProfilingEnabled |
|
{ |
|
return [[self settingForKey:kRCTDevSettingProfilingEnabled] boolValue]; |
|
} |
|
|
|
- (void)_profilingSettingDidChange |
|
{ |
|
BOOL enabled = self.isProfilingEnabled; |
|
if (_liveReloadURL && enabled != RCTProfileIsProfiling()) { |
|
if (enabled) { |
|
[_bridge startProfiling]; |
|
} else { |
|
[_bridge stopProfiling:^(NSData *logData) { |
|
RCTProfileSendResult(self->_bridge, @"systrace", logData); |
|
}]; |
|
} |
|
} |
|
} |
|
|
|
RCT_EXPORT_METHOD(setLiveReloadEnabled:(BOOL)enabled) |
|
{ |
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingLiveReloadEnabled]; |
|
[self _liveReloadSettingDidChange]; |
|
} |
|
|
|
- (BOOL)isLiveReloadEnabled |
|
{ |
|
return [[self settingForKey:kRCTDevSettingLiveReloadEnabled] boolValue]; |
|
} |
|
|
|
- (void)_liveReloadSettingDidChange |
|
{ |
|
BOOL liveReloadEnabled = (self.isLiveReloadAvailable && self.isLiveReloadEnabled); |
|
if (liveReloadEnabled) { |
|
[self _pollForLiveReload]; |
|
} else { |
|
[_liveReloadUpdateTask cancel]; |
|
_liveReloadUpdateTask = nil; |
|
} |
|
} |
|
|
|
RCT_EXPORT_METHOD(setHotLoadingEnabled:(BOOL)enabled) |
|
{ |
|
if (self.isHotLoadingEnabled != enabled) { |
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingHotLoadingEnabled]; |
|
[_bridge reload]; |
|
} |
|
} |
|
|
|
- (BOOL)isHotLoadingEnabled |
|
{ |
|
return [[self settingForKey:kRCTDevSettingHotLoadingEnabled] boolValue]; |
|
} |
|
|
|
RCT_EXPORT_METHOD(toggleElementInspector) |
|
{ |
|
BOOL value = [[self settingForKey:kRCTDevSettingIsInspectorShown] boolValue]; |
|
[self _updateSettingWithValue:@(!value) forKey:kRCTDevSettingIsInspectorShown]; |
|
|
|
if (_isJSLoaded) { |
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
|
[self.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; |
|
#pragma clang diagnostic pop |
|
} |
|
} |
|
|
|
- (void)toggleJSCSamplingProfiler |
|
{ |
|
JSGlobalContextRef globalContext = _bridge.jsContextRef; |
|
// JSPokeSamplingProfiler() toggles the profiling process |
|
JSValueRef jsResult = JSC_JSPokeSamplingProfiler(globalContext); |
|
|
|
if (JSC_JSValueGetType(globalContext, jsResult) != kJSTypeNull) { |
|
JSContext *context = [JSC_JSContext(globalContext) contextWithJSGlobalContextRef:globalContext]; |
|
NSString *results = [[JSC_JSValue(globalContext) valueWithJSValueRef:jsResult inContext:context] toObject]; |
|
RCTJSCSamplingProfiler *profilerModule = [_bridge moduleForClass:[RCTJSCSamplingProfiler class]]; |
|
[profilerModule operationCompletedWithResults:results]; |
|
} |
|
} |
|
|
|
- (BOOL)isElementInspectorShown |
|
{ |
|
return [[self settingForKey:kRCTDevSettingIsInspectorShown] boolValue]; |
|
} |
|
|
|
- (void)setIsPerfMonitorShown:(BOOL)isPerfMonitorShown |
|
{ |
|
[self _updateSettingWithValue:@(isPerfMonitorShown) forKey:kRCTDevSettingIsPerfMonitorShown]; |
|
} |
|
|
|
- (BOOL)isPerfMonitorShown |
|
{ |
|
return [[self settingForKey:kRCTDevSettingIsPerfMonitorShown] boolValue]; |
|
} |
|
|
|
- (void)setStartSamplingProfilerOnLaunch:(BOOL)startSamplingProfilerOnLaunch |
|
{ |
|
[self _updateSettingWithValue:@(startSamplingProfilerOnLaunch) forKey:kRCTDevSettingStartSamplingProfilerOnLaunch]; |
|
} |
|
|
|
- (BOOL)startSamplingProfilerOnLaunch |
|
{ |
|
return [[self settingForKey:kRCTDevSettingStartSamplingProfilerOnLaunch] boolValue]; |
|
} |
|
|
|
- (void)setExecutorClass:(Class)executorClass |
|
{ |
|
_executorClass = executorClass; |
|
if (_bridge.executorClass != executorClass) { |
|
|
|
// TODO (6929129): we can remove this special case test once we have better |
|
// support for custom executors in the dev menu. But right now this is |
|
// needed to prevent overriding a custom executor with the default if a |
|
// custom executor has been set directly on the bridge |
|
if (executorClass == Nil && |
|
_bridge.executorClass != objc_lookUpClass("RCTWebSocketExecutor")) { |
|
return; |
|
} |
|
|
|
_bridge.executorClass = executorClass; |
|
[_bridge reload]; |
|
} |
|
} |
|
|
|
#if RCT_DEV |
|
|
|
- (void)addHandler:(id<RCTPackagerClientMethod>)handler forPackagerMethod:(NSString *)name |
|
{ |
|
#if ENABLE_PACKAGER_CONNECTION |
|
[[RCTPackagerConnection sharedPackagerConnection] addHandler:handler forMethod:name]; |
|
#endif |
|
} |
|
|
|
#endif |
|
|
|
#pragma mark - Internal |
|
|
|
/** |
|
* Query the data source for all possible settings and make sure we're doing the right |
|
* thing for the state of each setting. |
|
*/ |
|
- (void)_synchronizeAllSettings |
|
{ |
|
[self _liveReloadSettingDidChange]; |
|
[self _remoteDebugSettingDidChange]; |
|
[self _profilingSettingDidChange]; |
|
} |
|
|
|
- (void)_pollForLiveReload |
|
{ |
|
if (!_isJSLoaded || ![[self settingForKey:kRCTDevSettingLiveReloadEnabled] boolValue] || !_liveReloadURL) { |
|
return; |
|
} |
|
|
|
if (_liveReloadUpdateTask) { |
|
return; |
|
} |
|
|
|
__weak RCTDevSettings *weakSelf = self; |
|
_liveReloadUpdateTask = [[NSURLSession sharedSession] dataTaskWithURL:_liveReloadURL completionHandler: |
|
^(__unused NSData *data, NSURLResponse *response, NSError *error) { |
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
__strong RCTDevSettings *strongSelf = weakSelf; |
|
if (strongSelf && [[strongSelf settingForKey:kRCTDevSettingLiveReloadEnabled] boolValue]) { |
|
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response; |
|
if (!error && HTTPResponse.statusCode == 205) { |
|
[strongSelf reload]; |
|
} else { |
|
if (error.code != NSURLErrorCancelled) { |
|
strongSelf->_liveReloadUpdateTask = nil; |
|
[strongSelf _pollForLiveReload]; |
|
} |
|
} |
|
} |
|
}); |
|
|
|
}]; |
|
|
|
[_liveReloadUpdateTask resume]; |
|
} |
|
|
|
- (void)jsLoaded:(NSNotification *)notification |
|
{ |
|
if (notification.userInfo[@"bridge"] != _bridge) { |
|
return; |
|
} |
|
|
|
_isJSLoaded = YES; |
|
|
|
// Check if live reloading is available |
|
NSURL *scriptURL = _bridge.bundleURL; |
|
if (![scriptURL isFileURL]) { |
|
// Live reloading is disabled when running from bundled JS file |
|
_liveReloadURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:scriptURL]; |
|
} else { |
|
_liveReloadURL = nil; |
|
} |
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
// update state again after the bridge has finished loading |
|
[self _synchronizeAllSettings]; |
|
|
|
// Inspector can only be shown after JS has loaded |
|
if ([self isElementInspectorShown]) { |
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
|
[self.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; |
|
#pragma clang diagnostic pop |
|
} |
|
}); |
|
} |
|
|
|
@end |
|
|
|
#else // #if RCT_DEV |
|
|
|
@implementation RCTDevSettings |
|
|
|
- (instancetype)initWithDataSource:(id<RCTDevSettingsDataSource>)dataSource { return [super init]; } |
|
- (BOOL)isHotLoadingAvailable { return NO; } |
|
- (BOOL)isLiveReloadAvailable { return NO; } |
|
- (BOOL)isRemoteDebuggingAvailable { return NO; } |
|
- (id)settingForKey:(NSString *)key { return nil; } |
|
- (void)reload {} |
|
- (void)toggleElementInspector {} |
|
- (void)toggleJSCSamplingProfiler {} |
|
|
|
@end |
|
|
|
#endif |
|
|
|
@implementation RCTBridge (RCTDevSettings) |
|
|
|
- (RCTDevSettings *)devSettings |
|
{ |
|
#if RCT_DEV |
|
return [self moduleForClass:[RCTDevSettings class]]; |
|
#else |
|
return nil; |
|
#endif |
|
} |
|
|
|
@end
|
|
|