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.
351 lines
10 KiB
351 lines
10 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 "RCTWebView.h" |
|
|
|
#import <UIKit/UIKit.h> |
|
|
|
#import "RCTAutoInsetsProtocol.h" |
|
#import "RCTConvert.h" |
|
#import "RCTEventDispatcher.h" |
|
#import "RCTLog.h" |
|
#import "RCTUtils.h" |
|
#import "RCTView.h" |
|
#import "UIView+React.h" |
|
|
|
NSString *const RCTJSNavigationScheme = @"react-js-navigation"; |
|
|
|
static NSString *const kPostMessageHost = @"postMessage"; |
|
|
|
@interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol> |
|
|
|
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; |
|
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; |
|
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError; |
|
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; |
|
@property (nonatomic, copy) RCTDirectEventBlock onMessage; |
|
|
|
@end |
|
|
|
@implementation RCTWebView |
|
{ |
|
UIWebView *_webView; |
|
NSString *_injectedJavaScript; |
|
} |
|
|
|
- (void)dealloc |
|
{ |
|
_webView.delegate = nil; |
|
} |
|
|
|
- (instancetype)initWithFrame:(CGRect)frame |
|
{ |
|
if ((self = [super initWithFrame:frame])) { |
|
super.backgroundColor = [UIColor clearColor]; |
|
_automaticallyAdjustContentInsets = YES; |
|
_contentInset = UIEdgeInsetsZero; |
|
_webView = [[UIWebView alloc] initWithFrame:self.bounds]; |
|
_webView.delegate = self; |
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ |
|
if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { |
|
_webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; |
|
} |
|
#endif |
|
[self addSubview:_webView]; |
|
} |
|
return self; |
|
} |
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) |
|
|
|
- (void)goForward |
|
{ |
|
[_webView goForward]; |
|
} |
|
|
|
- (void)goBack |
|
{ |
|
[_webView goBack]; |
|
} |
|
|
|
- (void)reload |
|
{ |
|
NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; |
|
if (request.URL && !_webView.request.URL.absoluteString.length) { |
|
[_webView loadRequest:request]; |
|
} |
|
else { |
|
[_webView reload]; |
|
} |
|
} |
|
|
|
- (void)stopLoading |
|
{ |
|
[_webView stopLoading]; |
|
} |
|
|
|
- (void)postMessage:(NSString *)message |
|
{ |
|
NSDictionary *eventInitDict = @{ |
|
@"data": message, |
|
}; |
|
NSString *source = [NSString |
|
stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", |
|
RCTJSONStringify(eventInitDict, NULL) |
|
]; |
|
[_webView stringByEvaluatingJavaScriptFromString:source]; |
|
} |
|
|
|
- (void)injectJavaScript:(NSString *)script |
|
{ |
|
[_webView stringByEvaluatingJavaScriptFromString:script]; |
|
} |
|
|
|
- (void)setSource:(NSDictionary *)source |
|
{ |
|
if (![_source isEqualToDictionary:source]) { |
|
_source = [source copy]; |
|
|
|
// Check for a static html source first |
|
NSString *html = [RCTConvert NSString:source[@"html"]]; |
|
if (html) { |
|
NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; |
|
if (!baseURL) { |
|
baseURL = [NSURL URLWithString:@"about:blank"]; |
|
} |
|
[_webView loadHTMLString:html baseURL:baseURL]; |
|
return; |
|
} |
|
|
|
NSURLRequest *request = [RCTConvert NSURLRequest:source]; |
|
// Because of the way React works, as pages redirect, we actually end up |
|
// passing the redirect urls back here, so we ignore them if trying to load |
|
// the same url. We'll expose a call to 'reload' to allow a user to load |
|
// the existing page. |
|
if ([request.URL isEqual:_webView.request.URL]) { |
|
return; |
|
} |
|
if (!request.URL) { |
|
// Clear the webview |
|
[_webView loadHTMLString:@"" baseURL:nil]; |
|
return; |
|
} |
|
[_webView loadRequest:request]; |
|
} |
|
} |
|
|
|
- (void)layoutSubviews |
|
{ |
|
[super layoutSubviews]; |
|
_webView.frame = self.bounds; |
|
} |
|
|
|
- (void)setContentInset:(UIEdgeInsets)contentInset |
|
{ |
|
_contentInset = contentInset; |
|
[RCTView autoAdjustInsetsForView:self |
|
withScrollView:_webView.scrollView |
|
updateOffset:NO]; |
|
} |
|
|
|
- (void)setScalesPageToFit:(BOOL)scalesPageToFit |
|
{ |
|
if (_webView.scalesPageToFit != scalesPageToFit) { |
|
_webView.scalesPageToFit = scalesPageToFit; |
|
[_webView reload]; |
|
} |
|
} |
|
|
|
- (BOOL)scalesPageToFit |
|
{ |
|
return _webView.scalesPageToFit; |
|
} |
|
|
|
- (void)setBackgroundColor:(UIColor *)backgroundColor |
|
{ |
|
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); |
|
self.opaque = _webView.opaque = (alpha == 1.0); |
|
_webView.backgroundColor = backgroundColor; |
|
} |
|
|
|
- (UIColor *)backgroundColor |
|
{ |
|
return _webView.backgroundColor; |
|
} |
|
|
|
- (NSMutableDictionary<NSString *, id> *)baseEvent |
|
{ |
|
NSMutableDictionary<NSString *, id> *event = [[NSMutableDictionary alloc] initWithDictionary:@{ |
|
@"url": _webView.request.URL.absoluteString ?: @"", |
|
@"loading" : @(_webView.loading), |
|
@"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], |
|
@"canGoBack": @(_webView.canGoBack), |
|
@"canGoForward" : @(_webView.canGoForward), |
|
}]; |
|
|
|
return event; |
|
} |
|
|
|
- (void)refreshContentInset |
|
{ |
|
[RCTView autoAdjustInsetsForView:self |
|
withScrollView:_webView.scrollView |
|
updateOffset:YES]; |
|
} |
|
|
|
#pragma mark - UIWebViewDelegate methods |
|
|
|
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request |
|
navigationType:(UIWebViewNavigationType)navigationType |
|
{ |
|
BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; |
|
|
|
static NSDictionary<NSNumber *, NSString *> *navigationTypes; |
|
static dispatch_once_t onceToken; |
|
dispatch_once(&onceToken, ^{ |
|
navigationTypes = @{ |
|
@(UIWebViewNavigationTypeLinkClicked): @"click", |
|
@(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", |
|
@(UIWebViewNavigationTypeBackForward): @"backforward", |
|
@(UIWebViewNavigationTypeReload): @"reload", |
|
@(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", |
|
@(UIWebViewNavigationTypeOther): @"other", |
|
}; |
|
}); |
|
|
|
// skip this for the JS Navigation handler |
|
if (!isJSNavigation && _onShouldStartLoadWithRequest) { |
|
NSMutableDictionary<NSString *, id> *event = [self baseEvent]; |
|
[event addEntriesFromDictionary: @{ |
|
@"url": (request.URL).absoluteString, |
|
@"navigationType": navigationTypes[@(navigationType)] |
|
}]; |
|
if (![self.delegate webView:self |
|
shouldStartLoadForRequest:event |
|
withCallback:_onShouldStartLoadWithRequest]) { |
|
return NO; |
|
} |
|
} |
|
|
|
if (_onLoadingStart) { |
|
// We have this check to filter out iframe requests and whatnot |
|
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; |
|
if (isTopFrame) { |
|
NSMutableDictionary<NSString *, id> *event = [self baseEvent]; |
|
[event addEntriesFromDictionary: @{ |
|
@"url": (request.URL).absoluteString, |
|
@"navigationType": navigationTypes[@(navigationType)] |
|
}]; |
|
_onLoadingStart(event); |
|
} |
|
} |
|
|
|
if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) { |
|
NSString *data = request.URL.query; |
|
data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "]; |
|
data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
|
|
|
NSMutableDictionary<NSString *, id> *event = [self baseEvent]; |
|
[event addEntriesFromDictionary: @{ |
|
@"data": data, |
|
}]; |
|
|
|
NSString *source = @"document.dispatchEvent(new MessageEvent('message:received'));"; |
|
|
|
[_webView stringByEvaluatingJavaScriptFromString:source]; |
|
|
|
_onMessage(event); |
|
} |
|
|
|
// JS Navigation handler |
|
return !isJSNavigation; |
|
} |
|
|
|
- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error |
|
{ |
|
if (_onLoadingError) { |
|
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { |
|
// NSURLErrorCancelled is reported when a page has a redirect OR if you load |
|
// a new URL in the WebView before the previous one came back. We can just |
|
// ignore these since they aren't real errors. |
|
// http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os |
|
return; |
|
} |
|
|
|
if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) { |
|
// Error code 102 "Frame load interrupted" is raised by the UIWebView if |
|
// its delegate returns FALSE from webView:shouldStartLoadWithRequest:navigationType |
|
// when the URL is from an http redirect. This is a common pattern when |
|
// implementing OAuth with a WebView. |
|
return; |
|
} |
|
|
|
NSMutableDictionary<NSString *, id> *event = [self baseEvent]; |
|
[event addEntriesFromDictionary:@{ |
|
@"domain": error.domain, |
|
@"code": @(error.code), |
|
@"description": error.localizedDescription, |
|
}]; |
|
_onLoadingError(event); |
|
} |
|
} |
|
|
|
- (void)webViewDidFinishLoad:(UIWebView *)webView |
|
{ |
|
if (_messagingEnabled) { |
|
#if RCT_DEV |
|
// See isNative in lodash |
|
NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')"; |
|
BOOL postMessageIsNative = [ |
|
[webView stringByEvaluatingJavaScriptFromString:testPostMessageNative] |
|
isEqualToString:@"true" |
|
]; |
|
if (!postMessageIsNative) { |
|
RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"); |
|
} |
|
#endif |
|
NSString *source = [NSString stringWithFormat: |
|
@"(function() {" |
|
"window.originalPostMessage = window.postMessage;" |
|
|
|
"var messageQueue = [];" |
|
"var messagePending = false;" |
|
|
|
"function processQueue() {" |
|
"if (!messageQueue.length || messagePending) return;" |
|
"messagePending = true;" |
|
"window.location = '%@://%@?' + encodeURIComponent(messageQueue.shift());" |
|
"}" |
|
|
|
"window.postMessage = function(data) {" |
|
"messageQueue.push(String(data));" |
|
"processQueue();" |
|
"};" |
|
|
|
"document.addEventListener('message:received', function(e) {" |
|
"messagePending = false;" |
|
"processQueue();" |
|
"});" |
|
"})();", RCTJSNavigationScheme, kPostMessageHost |
|
]; |
|
[webView stringByEvaluatingJavaScriptFromString:source]; |
|
} |
|
if (_injectedJavaScript != nil) { |
|
NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; |
|
|
|
NSMutableDictionary<NSString *, id> *event = [self baseEvent]; |
|
event[@"jsEvaluationValue"] = jsEvaluationValue; |
|
|
|
_onLoadingFinish(event); |
|
} |
|
// we only need the final 'finishLoad' call so only fire the event when we're actually done loading. |
|
else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { |
|
_onLoadingFinish([self baseEvent]); |
|
} |
|
} |
|
|
|
@end
|
|
|