FIRDLJavaScriptExecutor.m 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <WebKit/WebKit.h>
  17. #import "DynamicLinks/FIRDLJavaScriptExecutor.h"
  18. // define below needed because nullability of UIWebViewDelegate method param was changed between
  19. // iOS SDK versions
  20. #if (defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0))
  21. #define FIRDL_NULLABLE_IOS9_NONNULLABLE_IOS10 nonnull
  22. #else
  23. #define FIRDL_NULLABLE_IOS9_NONNULLABLE_IOS10 nullable
  24. #endif
  25. NS_ASSUME_NONNULL_BEGIN
  26. static NSString *const kJSMethodName = @"generateFingerprint";
  27. /** Creates and returns the FDL JS method name. */
  28. NSString *FIRDLTypeofFingerprintJSMethodNameString() {
  29. static NSString *methodName;
  30. static dispatch_once_t onceToken;
  31. dispatch_once(&onceToken, ^{
  32. methodName = [NSString stringWithFormat:@"typeof(%@)", kJSMethodName];
  33. });
  34. return methodName;
  35. }
  36. /** Creates and returns the FDL JS method definition. */
  37. NSString *GINFingerprintJSMethodString() {
  38. static NSString *methodString;
  39. static dispatch_once_t onceToken;
  40. dispatch_once(&onceToken, ^{
  41. methodString = [NSString stringWithFormat:@"%@()", kJSMethodName];
  42. });
  43. return methodString;
  44. }
  45. @interface FIRDLJavaScriptExecutor () <UIWebViewDelegate, WKNavigationDelegate>
  46. @end
  47. @implementation FIRDLJavaScriptExecutor {
  48. __weak id<FIRDLJavaScriptExecutorDelegate> _delegate;
  49. NSString *_script;
  50. // Web views with which to run JavaScript.
  51. UIWebView *_uiWebView; // Used in iOS 7 only.
  52. WKWebView *_wkWebView; // Used in iOS 8+ only.
  53. }
  54. - (instancetype)initWithDelegate:(id<FIRDLJavaScriptExecutorDelegate>)delegate
  55. script:(NSString *)script {
  56. NSParameterAssert(delegate);
  57. NSParameterAssert(script);
  58. NSParameterAssert(script.length > 0);
  59. NSAssert([NSThread isMainThread], @"%@ must be used in main thread",
  60. NSStringFromClass([self class]));
  61. if (self = [super init]) {
  62. _delegate = delegate;
  63. _script = [script copy];
  64. [self start];
  65. }
  66. return self;
  67. }
  68. #pragma mark - Internal methods
  69. - (void)start {
  70. NSString *htmlContent =
  71. [NSString stringWithFormat:@"<html><head><script>%@</script></head></html>", _script];
  72. // Use WKWebView if available as it executes JavaScript more quickly, otherwise, fall back
  73. // on UIWebView.
  74. if ([WKWebView class]) {
  75. _wkWebView = [[WKWebView alloc] init];
  76. _wkWebView.navigationDelegate = self;
  77. [_wkWebView loadHTMLString:htmlContent baseURL:nil];
  78. } else {
  79. _uiWebView = [[UIWebView alloc] init];
  80. _uiWebView.delegate = self;
  81. [_uiWebView loadHTMLString:htmlContent baseURL:nil];
  82. }
  83. }
  84. - (void)handleExecutionResult:(NSString *)result {
  85. [self cleanup];
  86. [_delegate javaScriptExecutor:self completedExecutionWithResult:result];
  87. }
  88. - (void)handleExecutionError:(nullable NSError *)error {
  89. [self cleanup];
  90. if (!error) {
  91. error = [NSError errorWithDomain:@"com.firebase.durabledeeplink" code:-1 userInfo:nil];
  92. }
  93. [_delegate javaScriptExecutor:self failedWithError:error];
  94. }
  95. - (void)cleanup {
  96. _uiWebView.delegate = nil;
  97. _uiWebView = nil;
  98. _wkWebView.navigationDelegate = nil;
  99. _wkWebView = nil;
  100. }
  101. #pragma mark - WKNavigationDelegate
  102. - (void)webView:(WKWebView *)webView
  103. didFinishNavigation:(null_unspecified WKNavigation *)navigation {
  104. __weak __typeof__(self) weakSelf = self;
  105. // Make sure that the javascript was loaded successfully before calling the method.
  106. [webView evaluateJavaScript:FIRDLTypeofFingerprintJSMethodNameString()
  107. completionHandler:^(id _Nullable typeofResult, NSError *_Nullable typeError) {
  108. if (typeError) {
  109. [weakSelf handleExecutionError:typeError];
  110. return;
  111. }
  112. if ([typeofResult isEqual:@"function"]) {
  113. [webView
  114. evaluateJavaScript:GINFingerprintJSMethodString()
  115. completionHandler:^(id _Nullable result, NSError *_Nullable functionError) {
  116. if ([result isKindOfClass:[NSString class]]) {
  117. [weakSelf handleExecutionResult:result];
  118. } else {
  119. [weakSelf handleExecutionError:nil];
  120. }
  121. }];
  122. } else {
  123. [weakSelf handleExecutionError:nil];
  124. }
  125. }];
  126. }
  127. - (void)webView:(WKWebView *)webView
  128. didFailNavigation:(null_unspecified WKNavigation *)navigation
  129. withError:(NSError *)error {
  130. [self handleExecutionError:error];
  131. }
  132. #pragma mark - UIWebViewDelegate
  133. - (void)webViewDidFinishLoad:(UIWebView *)webView {
  134. // Make sure that the javascript was loaded successfully before calling the method.
  135. NSString *methodType =
  136. [webView stringByEvaluatingJavaScriptFromString:FIRDLTypeofFingerprintJSMethodNameString()];
  137. if (![methodType isEqualToString:@"function"]) {
  138. // Javascript was not loaded successfully.
  139. [self handleExecutionError:nil];
  140. return;
  141. }
  142. // Get the result from javascript.
  143. NSString *result =
  144. [webView stringByEvaluatingJavaScriptFromString:GINFingerprintJSMethodString()];
  145. if ([result isKindOfClass:[NSString class]]) {
  146. [self handleExecutionResult:result];
  147. } else {
  148. [self handleExecutionError:nil];
  149. }
  150. }
  151. - (void)webView:(UIWebView *)webView
  152. didFailLoadWithError:(FIRDL_NULLABLE_IOS9_NONNULLABLE_IOS10 NSError *)error {
  153. [self handleExecutionError:error];
  154. }
  155. @end
  156. NS_ASSUME_NONNULL_END