Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit 94a18fa

Browse files
Fix cordova-ios 6 issue with file:// requests being rejected (#655)
* Fix white screen after applying update on cordova ios 6 * Fix IdentifierCodePushPath isn't available outside of the main class * Fix typo * Small updates * Remove unused import * Fix code style * Memoize check for CDVWebViewEngine existance
1 parent 1493d2b commit 94a18fa

8 files changed

Lines changed: 248 additions & 80 deletions

File tree

plugin.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@
9898
<header-file src="src/ios/Utilities.h" />
9999
<source-file src="src/ios/Utilities.m" />
100100
<source-file src="src/ios/CDVWKWebViewEngine+CodePush.m" />
101-
101+
<source-file src="src/ios/WebViewShared.m" />
102+
<source-file src="src/ios/WebViewShared.h" />
103+
<source-file src="src/ios/CDVViewController+CodePush.m" />
104+
102105
<!-- JWT -->
103106
<source-file src="src/ios/JWT/Core/Algorithms/Base/CodePushJWTAlgorithmFactory.m" />
104107
<source-file src="src/ios/JWT/Core/Algorithms/Base/CodePushJWTAlgorithmNone.m" />
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#import <Cordova/CDVViewController.h>
2+
#import <objc/runtime.h>
3+
#import "WebViewShared.h"
4+
#import "Utilities.h"
5+
6+
@interface CDVViewController (CodePush)
7+
- (NSURL*)appUrl; // Expose private method from original CDVViewController
8+
@end
9+
10+
#pragma clang diagnostic push
11+
#pragma clang diagnostic ignored "-Wincomplete-implementation"
12+
13+
@implementation CDVViewController (CodePush)
14+
15+
#pragma clang diagnostic pop
16+
17+
- (void)viewDidLoad_codepush
18+
{
19+
// Calls original viewDidLoad method from CDVViewController
20+
[self viewDidLoad_codepush];
21+
22+
/*
23+
In original viewDid method from CDVViewController at the end it tries to open start page using something like self.webViewEngine loadRequest [self appUrl].
24+
[self appUrl] is only resolves full path for startPage.
25+
For example
26+
27+
// CDVViewController.m
28+
...
29+
// self.startPage is set somewhere to @"index.html";
30+
31+
- (void)viewDidLoad {
32+
...
33+
NSURL* url = [self appUrl]; //manipulates with self.startPage
34+
//url is now @"file://HOSTNAME/path/to/index.html"
35+
}
36+
37+
It leads to WKWebView unable to load it because url prefixed with "file://" is rejected by web view for some reason.
38+
In practice it means that after applying CodePush update and restarting app blank screen is appeared.
39+
To workaround it [webViewShared loadRequest] is called after self.webViewEngine loadRequest is called in the original viewDidLoad method.
40+
Note 1: self.webViewEngine loadRequest will be failing anyway but calling [webViewShared loadRequest] will hide it for user.
41+
Note 2: It affects only cordova-ios 6 apps
42+
*/
43+
if([Utilities CDVWebViewEngineAvailable]) {
44+
WebViewShared* webViewShared = [WebViewShared getInstanceOrCreate:self.webViewEngine
45+
andCommandDelegate:self.commandDelegate
46+
andViewController:self];
47+
NSURL* url = [self appUrl];
48+
[webViewShared loadRequest:[NSURLRequest requestWithURL:url]];
49+
}
50+
}
51+
52+
// Swizzling original viewDidLoad method
53+
+ (void)load
54+
{
55+
static dispatch_once_t onceToken;
56+
dispatch_once(&onceToken, ^{
57+
Class class = [self class];
58+
59+
SEL originalSelector = @selector(viewDidLoad);
60+
SEL swizzledSelector = @selector(viewDidLoad_codepush);
61+
62+
Method originalMethod = class_getInstanceMethod(class, originalSelector);
63+
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
64+
65+
BOOL didAddMethod = class_addMethod(class,
66+
originalSelector,
67+
method_getImplementation(swizzledMethod),
68+
method_getTypeEncoding(swizzledMethod));
69+
70+
if (didAddMethod) {
71+
class_replaceMethod(class,
72+
swizzledSelector,
73+
method_getImplementation(originalMethod),
74+
method_getTypeEncoding(originalMethod));
75+
} else {
76+
method_exchangeImplementations(originalMethod, swizzledMethod);
77+
}
78+
});
79+
}
80+
81+
@end

src/ios/CDVWKWebViewEngine+CodePush.m

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,30 @@
11
#if defined(__has_include)
22
#if __has_include("CDVWKWebViewEngine.h")
33

4-
#import <Cordova/NSDictionary+CordovaPreferences.h>
54
#import "CDVWKWebViewEngine.h"
6-
#import "CodePush.h"
5+
#import "WebViewShared.h"
76

87
@implementation CDVWKWebViewEngine (CodePush)
98

10-
NSString* const IdentifierCodePushPath = @"codepush/deploy/versions";
119
NSString* lastLoadedURL = @"";
1210

1311
#pragma clang diagnostic push
1412
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
1513

1614
- (id)loadRequest:(NSURLRequest *)request {
1715
lastLoadedURL = request.URL.absoluteString;
18-
NSURL *readAccessURL;
19-
20-
NSURL* bundleURL = [[NSBundle mainBundle] bundleURL];
21-
if (![lastLoadedURL containsString:bundleURL.path] && ![lastLoadedURL containsString:IdentifierCodePushPath]) {
22-
return [self loadPluginRequest:request];
23-
}
24-
25-
if (request.URL.isFileURL) {
26-
// All file URL requests should be handled with the setServerBasePath in case if it is Ionic app.
27-
if ([CodePush hasIonicWebViewEngine: self]) {
28-
NSString* specifiedServerPath = [CodePush getCurrentServerBasePath];
29-
if (![specifiedServerPath containsString:IdentifierCodePushPath] || [request.URL.path containsString:IdentifierCodePushPath]) {
30-
[CodePush setServerBasePath:request.URL.path webView: self];
31-
}
32-
33-
return nil;
34-
}
35-
36-
if ([request.URL.absoluteString containsString:IdentifierCodePushPath]) {
37-
// If the app is attempting to load a CodePush update, then we can lock the WebView down to
38-
// just the CodePush "versions" directory. This prevents non-CodePush assets from being accessible,
39-
// while still allowing us to navigate to a future update, as well as to the binary if a rollback is needed.
40-
NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
41-
readAccessURL = [NSURL fileURLWithPathComponents:@[libraryPath, @"NoCloud", @"codepush", @"deploy", @"versions"]];
42-
} else {
43-
// In order to allow the WebView to be navigated from the app bundle to another location, we (for some
44-
// entirely unknown reason) need to ensure that the "read access URL" is set to the parent of the bundle
45-
// as opposed to the www folder, which is what the WKWebViewEngine would attempt to set it to by default.
46-
// If we didn't set this, then the attempt to navigate from the bundle to a CodePush update would fail.
47-
readAccessURL = [[[NSBundle mainBundle] bundleURL] URLByDeletingLastPathComponent];
48-
}
49-
50-
return [(WKWebView*)self.engineWebView loadFileURL:request.URL allowingReadAccessToURL:readAccessURL];
51-
} else {
52-
return [(WKWebView*)self.engineWebView loadRequest: request];
53-
}
54-
}
55-
56-
- (id)loadPluginRequest:(NSURLRequest *)request {
57-
if (request.URL.fileURL) {
58-
NSDictionary* settings = self.commandDelegate.settings;
59-
NSString *bind = [settings cordovaSettingForKey:@"Hostname"];
60-
if(bind == nil){
61-
bind = @"localhost";
62-
}
63-
NSString *scheme = [settings cordovaSettingForKey:@"iosScheme"];
64-
if(scheme == nil || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"file"]){
65-
scheme = @"ionic";
66-
}
67-
NSString *CDV_LOCAL_SERVER = [NSString stringWithFormat:@"%@://%@", scheme, bind];
68-
69-
NSURL* startURL = [NSURL URLWithString:((CDVViewController *)self.viewController).startPage];
70-
NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]];
71-
NSURL *url = [[NSURL URLWithString:CDV_LOCAL_SERVER] URLByAppendingPathComponent:request.URL.path];
72-
if ([request.URL.path isEqualToString:startFilePath]) {
73-
url = [NSURL URLWithString:CDV_LOCAL_SERVER];
74-
}
75-
if(request.URL.query) {
76-
url = [NSURL URLWithString:[@"?" stringByAppendingString:request.URL.query] relativeToURL:url];
77-
}
78-
if(request.URL.fragment) {
79-
url = [NSURL URLWithString:[@"#" stringByAppendingString:request.URL.fragment] relativeToURL:url];
80-
}
81-
request = [NSURLRequest requestWithURL:url];
82-
}
83-
return [(WKWebView*)self.engineWebView loadRequest:request];
16+
WebViewShared* webViewShared = [WebViewShared getInstanceOrCreate:self.webViewEngine
17+
andCommandDelegate:self.commandDelegate
18+
andViewController:self.viewController];
19+
return [webViewShared loadRequest:request];
8420
}
8521

8622
#pragma clang diagnostic pop
8723

8824
// Fix bug related to unable WKWebView recovery after reinit with loaded codepush update
8925
- (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error {
9026
// NSURLErrorFailingURLStringErrorKey is URL which caused a load to fail, if it's null then webView was terminated for some reason
91-
if ([[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey] == nil && [lastLoadedURL containsString:IdentifierCodePushPath]) {
27+
if ([[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey] == nil && [lastLoadedURL containsString:[WebViewShared getIdentifierCodePushPath]]) {
9228
NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]);
9329
NSLog(@"Trying to reload request with url: %@", lastLoadedURL);
9430
// Manually loading codepush start page via loadRequest method of this category

src/ios/CodePush.m

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#import "StatusReport.h"
1212
#import "UpdateHashUtils.h"
1313
#import "CodePushJWT.h"
14+
#import "WebViewShared.h"
1415

1516
@implementation CodePush
1617

@@ -21,6 +22,7 @@ @implementation CodePush
2122
NSString* const DeploymentKeyPreference = @"codepushdeploymentkey";
2223
NSString* const PublicKeyPreference = @"codepushpublickey";
2324
StatusReport* rollbackStatusReport = nil;
25+
WebViewShared* webViewShared = nil;
2426

2527
- (void)getBinaryHash:(CDVInvokedUrlCommand *)command {
2628
[self.commandDelegate runInBackground:^{
@@ -351,6 +353,9 @@ - (void)navigateToLocalDeploymentIfExists {
351353
}
352354

353355
- (void)pluginInitialize {
356+
webViewShared = [WebViewShared getInstanceOrCreate:self.webViewEngine
357+
andCommandDelegate:self.commandDelegate
358+
andViewController:self.viewController];
354359
// register for "on resume", "on pause" notifications
355360
[self clearDeploymentsIfBinaryUpdated];
356361
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
@@ -402,15 +407,20 @@ - (BOOL)loadPackage:(NSString*)packageLocation {
402407
}
403408

404409
- (void)loadURL:(NSURL*)url {
405-
// In order to make use of the "modern" Cordova platform, while still
406-
// maintaining back-compat with Cordova iOS 3.9.0, we need to conditionally
407-
// use the WebViewEngine for performing navigations only if the host app
408-
// is running 4.0.0+, and fallback to directly using the WebView otherwise.
409-
#if (WK_WEB_VIEW_ONLY && defined(__CORDOVA_4_0_0)) || defined(__CORDOVA_4_0_0)
410-
[self.webViewEngine loadRequest:[NSURLRequest requestWithURL:url]];
411-
#else
412-
[(UIWebView*)self.webView loadRequest:[NSURLRequest requestWithURL:url]];
413-
#endif
410+
// Fix file:// requests issues for cordova-ios 6+
411+
if([Utilities CDVWebViewEngineAvailable]) {
412+
[webViewShared loadRequest:[NSURLRequest requestWithURL:url]];
413+
} else {
414+
// In order to make use of the "modern" Cordova platform, while still
415+
// maintaining back-compat with Cordova iOS 3.9.0, we need to conditionally
416+
// use the WebViewEngine for performing navigations only if the host app
417+
// is running 4.0.0+, and fallback to directly using the WebView otherwise.
418+
#if (WK_WEB_VIEW_ONLY && defined(__CORDOVA_4_0_0)) || defined(__CORDOVA_4_0_0)
419+
[self.webViewEngine loadRequest:[NSURLRequest requestWithURL:url]];
420+
#else
421+
[(UIWebView*)self.webView loadRequest:[NSURLRequest requestWithURL:url]];
422+
#endif
423+
}
414424
}
415425

416426
+ (Boolean) hasIonicWebViewEngine:(id<CDVWebViewEngineProtocol>) webViewEngine {

src/ios/Utilities.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
+ (NSString*)getApplicationVersion;
44
+ (NSString*)getApplicationTimestamp;
55
+ (NSDate*)getApplicationBuildTime;
6+
+ (BOOL)CDVWebViewEngineAvailable;
67

78
@end

src/ios/Utilities.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
@implementation Utilities
44

5+
static BOOL CDVWebViewEngineChecked = NO;
6+
static BOOL CDVWebViewEngineExists = NO;
7+
58
+ (NSString*)getApplicationVersion{
69
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
710
}
@@ -24,6 +27,14 @@ + (NSDate*)getApplicationBuildTime{
2427
return fileDate;
2528
}
2629

30+
+ (BOOL)CDVWebViewEngineAvailable{
31+
if (!CDVWebViewEngineChecked) {
32+
CDVWebViewEngineChecked = YES;
33+
CDVWebViewEngineExists = NSClassFromString(@"CDVWebViewEngine") != nil;
34+
}
35+
return CDVWebViewEngineExists;
36+
}
37+
2738
void CPLog(NSString *formatString, ...) {
2839
va_list args;
2940
va_start(args, formatString);

src/ios/WebViewShared.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import <Cordova/CDVWebViewEngineProtocol.h>
2+
#import <Cordova/CDVCommandDelegate.h>
3+
4+
@interface WebViewShared : NSObject
5+
6+
@property id<CDVCommandDelegate> commandDelegate;
7+
@property id<CDVWebViewEngineProtocol> webViewEngine;
8+
@property UIViewController *viewController;
9+
10+
+ (id)getInstanceOrCreate:(id<CDVWebViewEngineProtocol>)webViewEngine andCommandDelegate:(id<CDVCommandDelegate>)commandDelegate andViewController:(UIViewController *)viewController;
11+
+ (NSString *)getIdentifierCodePushPath;
12+
- (id)initWithWebViewEngine:(id<CDVWebViewEngineProtocol>)webViewEngine andCommandDelegate:(id<CDVCommandDelegate>)commandDelegate andViewController:(UIViewController *)viewController;
13+
- (id)loadRequest:(NSURLRequest *)request;
14+
- (id)loadIonicPluginRequest:(NSURLRequest *)request;
15+
16+
@end

0 commit comments

Comments
 (0)