Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iOS): Refactoring configuration #3759

Merged
merged 23 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
25246d0
First pass implementation of configuration objects and parsing.
ikeith Nov 2, 2020
99127f0
Adding type information to allowed navigation array.
ikeith Nov 3, 2020
eeb5f4c
Refactored initialization, bridge view controller, and bridge to use …
ikeith Nov 3, 2020
658d023
Removing CAPConfig file.
ikeith Nov 3, 2020
b641606
Adding unit tests for configuration parsing.
ikeith Nov 4, 2020
e30e927
Fixing logic bugs exposed by testing.
ikeith Nov 4, 2020
374b6d1
Merge branch 'main' into configuration
ikeith Nov 11, 2020
99f50e7
Fix swiftlint errors.
ikeith Nov 12, 2020
6660484
Swiftlint formatting
ikeith Nov 12, 2020
c652616
Merge branch 'main' into configuration
ikeith Nov 12, 2020
0977e83
Merge branch 'main' into configuration
ikeith Nov 16, 2020
a844c9a
Merge branch 'main' into configuration
ikeith Nov 18, 2020
a4365ac
Merge branch 'main' into configuration
ikeith Nov 19, 2020
e90f763
Updating swiftlint exclusions.
ikeith Nov 19, 2020
8ea11fe
Merge branch 'main' into configuration
ikeith Nov 19, 2020
c108546
Merge branch 'main' into configuration
ikeith Nov 19, 2020
51ad4d6
Merge branch 'main' into configuration
ikeith Nov 20, 2020
1daa7f3
Merge branch 'main' into configuration
ikeith Nov 20, 2020
250979f
Merge branch 'main' into configuration
ikeith Nov 20, 2020
bc764db
Removing unnecessary forward declaration.
ikeith Nov 20, 2020
57549cc
Merge branch 'main' into configuration
ikeith Nov 23, 2020
043adb2
Merge branch 'main' into configuration
ikeith Nov 24, 2020
76b9c01
Merge branch 'main' into configuration
imhoffd Nov 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 63 additions & 12 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import WebKit
@objc public protocol CAPBridgeProtocol: NSObjectProtocol {
// MARK: Environment Properties
var viewController: UIViewController? { get }
var config: CAPConfig { get }
var config: InstanceConfiguration { get }
var webView: WKWebView? { get }
var isSimEnvironment: Bool { get }
var isDevEnvironment: Bool { get }
Expand Down
156 changes: 82 additions & 74 deletions ios/Capacitor/Capacitor/CAPBridgeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
public var bridgedViewController: UIViewController? {
return self
}
public let cordovaParser = CDVConfigParser.init()
private var hostname: String?
private var allowNavigationConfig: [String]?
private var basePath: String = ""
Expand All @@ -37,7 +36,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
@objc public var supportedOrientations: [Int] = []

@objc public var startDir = ""
@objc public var config: String?

// Construct the Capacitor runtime
private var capacitorBridge: CapacitorBridge?
Expand All @@ -47,79 +45,92 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
private var handler: CAPAssetHandler?

override public func loadView() {
let configUrl = Bundle.main.url(forResource: "config", withExtension: "xml")
let configParser = XMLParser(contentsOf: configUrl!)!
configParser.delegate = cordovaParser
configParser.parse()
guard let startPath = self.getStartPath() else {
// load the configuration and set the logging flag
let configDescriptor = InstanceDescriptor.init()
let configuration = InstanceConfiguration(with: configDescriptor)
CAPLog.enableLogging = configuration.enableLogging
logWarnings(for: configDescriptor)

// get the starting path and configure our environment
guard let startPath = self.getStartPath(deployDisabled: configuration.cordovaDeployDisabled) else {
return
}

setStatusBarDefaults()
setScreenOrientationDefaults()
let capConfig = CAPConfig(self.config)


// get the web view
let assetHandler = CAPAssetHandler()
assetHandler.setAssetPath(startPath)
webView = prepareWebView(with: configuration, assetHandler: assetHandler)
view = webView
self.handler = assetHandler
// configure the web view
setKeyboardRequiresUserInteraction(false)
// create the bridge
let messageHandler = CAPMessageHandlerWrapper()
capacitorBridge = CapacitorBridge(with: configuration, delegate: self, cordovaConfiguration: configDescriptor.cordovaConfiguration, messageHandler: messageHandler)
}

private func prepareWebView(with configuration: InstanceConfiguration, assetHandler: CAPAssetHandler) -> WKWebView {
// set the cookie policy
HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always
// setup the web view configuration
let webViewConfiguration = WKWebViewConfiguration()
let messageHandler = CAPMessageHandlerWrapper()
self.handler = CAPAssetHandler()
self.handler!.setAssetPath(startPath)
var specifiedScheme = CapacitorBridge.defaultScheme
let configScheme = capConfig.getString("server.iosScheme") ?? CapacitorBridge.defaultScheme
// check if WebKit handles scheme and if it is valid according to Apple's documentation
if !WKWebView.handlesURLScheme(configScheme) && configScheme.range(of: "^[a-z][a-z0-9.+-]*$", options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil {
specifiedScheme = configScheme.lowercased()
}
webViewConfiguration.setURLSchemeHandler(self.handler, forURLScheme: specifiedScheme)
webViewConfiguration.userContentController = messageHandler.contentController

configureWebView(configuration: webViewConfiguration)

if let appendUserAgent = (capConfig.getValue("ios.appendUserAgent") as? String) ?? (capConfig.getValue("appendUserAgent") as? String) {
webViewConfiguration.allowsInlineMediaPlayback = true
webViewConfiguration.suppressesIncrementalRendering = false
webViewConfiguration.allowsAirPlayForMediaPlayback = true
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
if let appendUserAgent = configuration.appendedUserAgentString {
webViewConfiguration.applicationNameForUserAgent = appendUserAgent
}

webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
webView?.scrollView.bounces = false
let availableInsets = ["automatic", "scrollableAxes", "never", "always"]
if let contentInset = (capConfig.getValue("ios.contentInset") as? String),
let index = availableInsets.firstIndex(of: contentInset) {
webView?.scrollView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: index)!
} else {
webView?.scrollView.contentInsetAdjustmentBehavior = .never
}

webView?.uiDelegate = self
webView?.navigationDelegate = self
if let allowsLinkPreview = (capConfig.getValue("ios.allowsLinkPreview") as? Bool) {
webView?.allowsLinkPreview = allowsLinkPreview
}
webView?.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
view = webView

setKeyboardRequiresUserInteraction(false)

capacitorBridge = CapacitorBridge(self, messageHandler, capConfig, specifiedScheme)

if let scrollEnabled = bridge!.config.getValue("ios.scrollEnabled") as? Bool {
webView?.scrollView.isScrollEnabled = scrollEnabled
}

if let backgroundColor = (bridge!.config.getValue("ios.backgroundColor") as? String) ?? (bridge!.config.getValue("backgroundColor") as? String) {
webView?.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor)
webView?.scrollView.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor)
} else if #available(iOS 13, *) {
webViewConfiguration.setURLSchemeHandler(assetHandler, forURLScheme: configuration.localURL.scheme ?? InstanceDescriptorDefaults.scheme)
let messageHandler = CAPMessageHandlerWrapper()
webViewConfiguration.userContentController = messageHandler.contentController
// create the web view and set its properties
let webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
webView.scrollView.bounces = false
webView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior
webView.uiDelegate = self
webView.navigationDelegate = self
webView.allowsLinkPreview = configuration.allowLinkPreviews
webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
webView.scrollView.isScrollEnabled = configuration.enableScrolling
if let overrideUserAgent = configuration.overridenUserAgentString {
webView.customUserAgent = overrideUserAgent
}
if let backgroundColor = configuration.backgroundColor {
webView.backgroundColor = backgroundColor
webView.scrollView.backgroundColor = backgroundColor
}
else if #available(iOS 13, *) {
// Use the system background colors if background is not set by user
webView?.backgroundColor = UIColor.systemBackground
webView?.scrollView.backgroundColor = UIColor.systemBackground
webView.backgroundColor = UIColor.systemBackground
webView.scrollView.backgroundColor = UIColor.systemBackground
}

if let overrideUserAgent = (bridge!.config.getValue("ios.overrideUserAgent") as? String) ?? (bridge!.config.getValue("overrideUserAgent") as? String) {
webView?.customUserAgent = overrideUserAgent
return webView
}

private func logWarnings(for descriptor: InstanceDescriptor) {
if descriptor.warnings.contains(.missingAppDir) {
CAPLog.print("⚡️ ERROR: Unable to find application directory at: \"\(descriptor.appLocation.absoluteString)\"!")
}
if descriptor.instanceType == .fixed {
if descriptor.warnings.contains(.missingFile) {
CAPLog.print("Unable to find capacitor.config.json, make sure it exists and run npx cap copy.")
}
if descriptor.warnings.contains(.invalidFile) {
CAPLog.print("Unable to parse capacitor.config.json. Make sure it's valid JSON.")
}
if descriptor.warnings.contains(.missingCordovaFile) {
CAPLog.print("Unable to find config.xml, make sure it exists and run npx cap copy.")
}
if descriptor.warnings.contains(.invalidCordovaFile) {
CAPLog.print("Unable to parse config.xml. Make sure it's valid XML.")
}
}
}

private func getStartPath() -> String? {
private func getStartPath(deployDisabled: Bool = false) -> String? {
var resourcesPath = assetsFolder
if !startDir.isEmpty {
resourcesPath = URL(fileURLWithPath: resourcesPath).appendingPathComponent(startDir).relativePath
Expand All @@ -130,7 +141,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
return nil
}

if !isDeployDisabled() && !isNewBinary() {
if !deployDisabled && !isNewBinary() {
let defaults = UserDefaults.standard
let persistedPath = defaults.string(forKey: "serverBasePath")
if persistedPath != nil && !persistedPath!.isEmpty {
Expand All @@ -145,11 +156,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
return startPath
}

func isDeployDisabled() -> Bool {
let val = cordovaParser.settings.object(forKey: "DisableDeploy".lowercased()) as? NSString
return val?.boolValue ?? false
}

func isNewBinary() -> Bool {
if let plist = Bundle.main.infoDictionary {
if let versionCode = plist["CFBundleVersion"] as? String, let versionName = plist["CFBundleShortVersionString"] as? String {
Expand Down Expand Up @@ -203,13 +209,15 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
if Bundle.main.path(forResource: fullStartPath.relativePath, ofType: "html") == nil {
fatalLoadError()
}

hostname = capacitorBridge!.config.getString("server.url") ?? "\(capacitorBridge!.getLocalUrl())"
allowNavigationConfig = bridge!.config.getValue("server.allowNavigation") as? [String]


guard let url = bridge?.config.serverURL else {
CAPLog.print("⚡️ Unable to load app: Missing URL!")
return
}
hostname = url.absoluteString

CAPLog.print("⚡️ Loading app at \(hostname!)...")
let request = URLRequest(url: URL(string: hostname!)!)
_ = webView?.load(request)
_ = webView?.load(URLRequest(url: url))
}

func setServerPath(path: String) {
Expand Down
89 changes: 0 additions & 89 deletions ios/Capacitor/Capacitor/CAPConfig.swift

This file was deleted.

29 changes: 29 additions & 0 deletions ios/Capacitor/Capacitor/CAPInstanceConfiguration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef CAPInstanceConfiguration_h
#define CAPInstanceConfiguration_h

@import UIKit;

@class CAPInstanceDescriptor;

NS_SWIFT_NAME(InstanceConfiguration)
@interface CAPInstanceConfiguration: NSObject
@property (nonatomic, readonly, nullable) NSString *appendedUserAgentString;
@property (nonatomic, readonly, nullable) NSString *overridenUserAgentString;
@property (nonatomic, readonly, nullable) UIColor *backgroundColor;
@property (nonatomic, readonly, nonnull) NSArray<NSString*> *allowedNavigationHostnames;
@property (nonatomic, readonly, nonnull) NSURL *localURL;
@property (nonatomic, readonly, nonnull) NSURL *serverURL;
@property (nonatomic, readonly, nonnull) NSDictionary *pluginConfigurations;
@property (nonatomic, readonly) BOOL enableLogging;
@property (nonatomic, readonly) BOOL enableScrolling;
@property (nonatomic, readonly) BOOL allowLinkPreviews;
@property (nonatomic, readonly) BOOL cordovaDeployDisabled;
@property (nonatomic, readonly) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
@property (nonatomic, readonly, nonnull) NSURL *appLocation;

@property (nonatomic, readonly, nonnull) NSDictionary *legacyConfig DEPRECATED_MSG_ATTRIBUTE("Use direct properties instead");

- (instancetype _Nonnull)initWithDescriptor:(CAPInstanceDescriptor* _Nonnull)descriptor NS_SWIFT_NAME(init(with:));
@end

#endif /* CAPInstanceConfiguration_h */
42 changes: 42 additions & 0 deletions ios/Capacitor/Capacitor/CAPInstanceConfiguration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#import "CAPInstanceConfiguration.h"
#import <Capacitor/Capacitor-Swift.h>

@implementation CAPInstanceConfiguration

- (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor {
if (self = [super init]) {
// first, give the descriptor a chance to make itself internally consistent
[descriptor normalize];
// now copy the simple properties
_appendedUserAgentString = descriptor.appendedUserAgentString;
_overridenUserAgentString = descriptor.overridenUserAgentString;
_backgroundColor = descriptor.backgroundColor;
_allowedNavigationHostnames = descriptor.allowedNavigationHostnames;
_enableLogging = descriptor.enableLogging;
_enableScrolling = descriptor.enableScrolling;
_allowLinkPreviews = descriptor.allowLinkPreviews;
_contentInsetAdjustmentBehavior = descriptor.contentInsetAdjustmentBehavior;
_appLocation = descriptor.appLocation;
_pluginConfigurations = descriptor.pluginConfigurations;
_legacyConfig = descriptor.legacyConfig;
// construct the necessary URLs
_localURL = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@://%@", descriptor.urlScheme, descriptor.urlHostname]];
if (descriptor.serverURL != nil) {
_serverURL = [[NSURL alloc] initWithString:(descriptor.serverURL)];
}
else {
_serverURL = _localURL;
}
// extract the one value we care about from the cordova configuration
id value = [descriptor.cordovaConfiguration.settings objectForKey:[@"DisableDeploy" lowercaseString]];
if (value != nil && [value isKindOfClass:[NSString class]]) {
_cordovaDeployDisabled = [(NSString*)value boolValue];
}
else {
_cordovaDeployDisabled = false;
}
}
return self;
}

@end
Loading