From e3b858c55be6eeb259422239e895af8012f1c436 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 20 May 2024 15:48:15 -0400 Subject: [PATCH 01/10] Start of cordova removal --- .../CAPBridgeViewController+CDVScreenOrientationDelegate.h | 6 ------ .../CAPBridgeViewController+CDVScreenOrientationDelegate.m | 5 ----- .../CAPBridgeViewController+CDVScreenOrientationDelegate.h | 6 ++++++ .../CAPBridgeViewController+CDVScreenOrientationDelegate.m | 5 +++++ 4 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h delete mode 100644 ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m create mode 100644 ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.h create mode 100644 ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.m diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h b/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h deleted file mode 100644 index aeda727d5..000000000 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h +++ /dev/null @@ -1,6 +0,0 @@ -#import - -@interface CAPBridgeViewController (CDVScreenOrientationDelegate) - -@end - diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m b/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m deleted file mode 100644 index 94c169a0c..000000000 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m +++ /dev/null @@ -1,5 +0,0 @@ -#import "CAPBridgeViewController+CDVScreenOrientationDelegate.h" - -@implementation CAPBridgeViewController (CDVScreenOrientationDelegate) - -@end diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.h b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.h new file mode 100644 index 000000000..ba82a3062 --- /dev/null +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.h @@ -0,0 +1,6 @@ +//#import +// +//@interface CAPBridgeViewController (CDVScreenOrientationDelegate) +// +//@end + diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.m b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.m new file mode 100644 index 000000000..690a665b1 --- /dev/null +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CAPBridgeViewController+CDVScreenOrientationDelegate.m @@ -0,0 +1,5 @@ +//#import "CAPBridgeViewController+CDVScreenOrientationDelegate.h" +// +//@implementation CAPBridgeViewController (CDVScreenOrientationDelegate) +// +//@end From a826c8580d39113836c3fe7ff284786baad57e71 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 20 May 2024 15:48:59 -0400 Subject: [PATCH 02/10] Add files --- ios/Capacitor.podspec | 1 - .../CAPApplicationDelegateProxy.swift | 2 +- .../Capacitor/CAPBridgeViewController.swift | 2 - .../Capacitor/CAPInstanceDescriptor.h | 4 +- .../Capacitor/CAPInstanceDescriptor.m | 2 +- .../Capacitor/CAPInstanceDescriptor.swift | 33 ++--- ios/Capacitor/Capacitor/CapacitorBridge.swift | 130 +++++++++--------- .../Capacitor/WebViewDelegationHandler.swift | 29 ++-- ios/CapacitorCordova.podspec | 2 +- 9 files changed, 105 insertions(+), 100 deletions(-) diff --git a/ios/Capacitor.podspec b/ios/Capacitor.podspec index f55f30dc8..882cdfb25 100644 --- a/ios/Capacitor.podspec +++ b/ios/Capacitor.podspec @@ -19,6 +19,5 @@ Pod::Spec.new do |s| s.source_files = "#{prefix}Capacitor/Capacitor/**/*.{swift,h,m}" s.module_map = "#{prefix}Capacitor/Capacitor/Capacitor.modulemap" s.resources = ["#{prefix}Capacitor/Capacitor/assets/native-bridge.js", "#{prefix}Capacitor/Capacitor/PrivacyInfo.xcprivacy"] - s.dependency 'CapacitorCordova' s.swift_version = '5.1' end diff --git a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift index 7f510bc5a..50027babc 100644 --- a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift +++ b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift @@ -11,7 +11,7 @@ public class ApplicationDelegateProxy: NSObject, UIApplicationDelegate { "url": url, "options": options ]) - NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: url) + //NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: url) lastURL = url return true } diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index 37ef75c4d..9c74c44ef 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -1,6 +1,5 @@ import UIKit import WebKit -import Cordova @objc open class CAPBridgeViewController: UIViewController { private var capacitorBridge: CapacitorBridge? @@ -47,7 +46,6 @@ import Cordova // create the bridge capacitorBridge = CapacitorBridge(with: configuration, delegate: self, - cordovaConfiguration: configDescriptor.cordovaConfiguration, assetHandler: assetHandler, delegationHandler: delegationHandler) capacitorDidLoad() diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h index 228b3a41d..876941543 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h @@ -2,7 +2,7 @@ #define CAPInstanceDescriptor_h @import UIKit; -@import Cordova; + typedef NS_ENUM(NSInteger, CAPInstanceType) { CAPInstanceTypeFixed NS_SWIFT_NAME(fixed), @@ -131,7 +131,7 @@ NS_SWIFT_NAME(InstanceDescriptor) /** @brief The parser used to load the cofiguration for Cordova plugins. */ -@property (nonatomic, copy, nonnull) CDVConfigParser *cordovaConfiguration; +@property (nonatomic, copy, nonnull) NSObject *cordovaConfiguration; /** @brief Warnings generated during initialization. */ diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m index 6d3566776..65a87d05a 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m @@ -44,7 +44,7 @@ - (void)_setDefaultsWithAppLocation:(NSURL*)location { _contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; _appLocation = location; _limitsNavigationsToAppBoundDomains = FALSE; - _cordovaConfiguration = [[CDVConfigParser alloc] init]; + // _cordovaConfiguration = [[CDVConfigParser alloc] init]; _warnings = 0; if (location == nil) { _warnings |= CAPInstanceWarningMissingAppDir; diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift index 9645d005e..574794bda 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift @@ -53,21 +53,21 @@ internal extension InstanceDescriptor { } // parse the cordova configuration - var configParser: XMLParser? - if let cordovaURL = cordovaURL, - FileManager.default.fileExists(atPath: cordovaURL.path, isDirectory: &isDirectory), - isDirectory.boolValue == false { - configParser = XMLParser(contentsOf: cordovaURL) - } else { - warnings.update(with: .missingCordovaFile) - // we don't want to break up string literals - // swiftlint:disable:next line_length - if let cordovaXML = "".data(using: .utf8) { - configParser = XMLParser(data: cordovaXML) - } - } - configParser?.delegate = cordovaConfiguration - configParser?.parse() +// var configParser: XMLParser? +// if let cordovaURL = cordovaURL, +// FileManager.default.fileExists(atPath: cordovaURL.path, isDirectory: &isDirectory), +// isDirectory.boolValue == false { +// configParser = XMLParser(contentsOf: cordovaURL) +// } else { +// warnings.update(with: .missingCordovaFile) +// // we don't want to break up string literals +// // swiftlint:disable:next line_length +// if let cordovaXML = "".data(using: .utf8) { +// configParser = XMLParser(data: cordovaXML) +// } +// } +// configParser?.delegate = cordovaConfiguration +// configParser?.parse() // extract our configuration values if let config = config { @@ -149,7 +149,8 @@ internal extension InstanceDescriptor { extension InstanceDescriptor { @objc public var cordovaDeployDisabled: Bool { - return (cordovaConfiguration.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false + return true + //return (cordovaConfiguration.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false } @objc public func normalize() { diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 644076cec..c100a407a 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -1,7 +1,6 @@ import Foundation import Dispatch import WebKit -import Cordova internal typealias CapacitorPlugin = CAPPlugin & CAPBridgedPlugin @@ -111,12 +110,12 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { // Map of all loaded and instantiated plugins by pluginId -> instance var plugins = [String: CapacitorPlugin]() // Manager for getting Cordova plugins - var cordovaPluginManager: CDVPluginManager? + //var cordovaPluginManager: CDVPluginManager? // Calls we are storing to resolve later var storedCalls = [String: CAPPluginCall]() // Whether to inject the Cordova files private var injectCordovaFiles = false - private var cordovaParser: CDVConfigParser? + //private var cordovaParser: CDVConfigParser? // Background dispatch queue for plugin calls open private(set) var dispatchQueue = DispatchQueue(label: "bridge") @@ -196,12 +195,19 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { // MARK: - Initialization - public init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) { + + + @available(*, deprecated, renamed: "init", message: "Use different init") + public convenience init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: Any, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) { + self.init(with: configuration, delegate: bridgeDelegate, assetHandler: assetHandler, delegationHandler: delegationHandler, autoRegisterPlugins: autoRegisterPlugins) + } + + public init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) { + self.bridgeDelegate = bridgeDelegate self.webViewAssetHandler = assetHandler self.webViewDelegationHandler = delegationHandler self.config = configuration - self.cordovaParser = cordovaConfiguration self.notificationRouter = NotificationRouter() self.notificationRouter.handleApplicationNotifications = configuration.handleApplicationNotifications self.autoRegisterPlugins = autoRegisterPlugins @@ -211,7 +217,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { exportCoreJS(localUrl: configuration.localURL.absoluteString) registerPlugins() - setupCordovaCompatibility() + // setupCordovaCompatibility() observers.append(NotificationCenter.default.addObserver(forName: type(of: self).tmpVCAppeared.name, object: .none, queue: .none) { [weak self] _ in self?.tmpWindow = nil }) @@ -246,19 +252,19 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { /** Set up our Cordova compat by loading all known Cordova plugins and injecting their JS. */ - func setupCordovaCompatibility() { - if injectCordovaFiles { - exportCordovaJS() - registerCordovaPlugins() - } else { - observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in - self?.triggerDocumentJSEvent(eventName: "resume") - }) - observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in - self?.triggerDocumentJSEvent(eventName: "pause") - }) - } - } +// func setupCordovaCompatibility() { +// if injectCordovaFiles { +// exportCordovaJS() +// // registerCordovaPlugins() +// } else { +// observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in +// self?.triggerDocumentJSEvent(eventName: "resume") +// }) +// observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in +// self?.triggerDocumentJSEvent(eventName: "pause") +// }) +// } +// } /** Export the core Cordova JS runtime @@ -293,11 +299,11 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { for plugin in registrationList.packageClassList { if let pluginClass = NSClassFromString(plugin) { - if pluginClass == CDVPlugin.self { - injectCordovaFiles = true - } else { +// if pluginClass == CDVPlugin.self { +// injectCordovaFiles = true +// } else { pluginList.append(pluginClass) - } +// } } } } @@ -405,22 +411,22 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { return self.dispatchQueue } - func registerCordovaPlugins() { - guard let cordovaParser = cordovaParser else { - return - } - cordovaPluginManager = CDVPluginManager.init(parser: cordovaParser, viewController: self.viewController, webView: self.getWebView()) - if cordovaParser.startupPluginNames.count > 0 { - for pluginName in cordovaParser.startupPluginNames { - _ = cordovaPluginManager?.getCommandInstance(pluginName as? String) - } - } - do { - try JSExport.exportCordovaPluginsJS(userContentController: webViewDelegationHandler.contentController) - } catch { - type(of: self).fatalError(error, error) - } - } +// func registerCordovaPlugins() { +// guard let cordovaParser = cordovaParser else { +// return +// } +// cordovaPluginManager = CDVPluginManager.init(parser: cordovaParser, viewController: self.viewController, webView: self.getWebView()) +// if cordovaParser.startupPluginNames.count > 0 { +// for pluginName in cordovaParser.startupPluginNames { +// _ = cordovaPluginManager?.getCommandInstance(pluginName as? String) +// } +// } +// do { +// try JSExport.exportCordovaPluginsJS(userContentController: webViewDelegationHandler.contentController) +// } catch { +// type(of: self).fatalError(error, error) +// } +// } func reload() { self.getWebView()?.reload() @@ -524,29 +530,29 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { Handle a Cordova call from JavaScript. First, find the corresponding plugin, construct a selector, and perform that selector on the plugin instance. */ - func handleCordovaJSCall(call: JSCall) { - // Create a selector to send to the plugin - - if let plugin = self.cordovaPluginManager?.getCommandInstance(call.pluginId.lowercased()) { - let selector = NSSelectorFromString("\(call.method):") - if !plugin.responds(to: selector) { - CAPLog.print("Error: Plugin \(plugin.className ?? "") does not respond to method call \(selector).") - CAPLog.print("Ensure plugin method exists and uses @objc in its declaration") - return - } - - let arguments: [Any] = call.options["options"] as? [Any] ?? [] - let pluginCall = CDVInvokedUrlCommand(arguments: arguments, - callbackId: call.callbackId, - className: plugin.className, - methodName: call.method) - plugin.perform(selector, with: pluginCall) - - } else { - CAPLog.print("Error: Cordova Plugin mapping not found") - return - } - } +// func handleCordovaJSCall(call: JSCall) { +// // Create a selector to send to the plugin +// +// if let plugin = self.cordovaPluginManager?.getCommandInstance(call.pluginId.lowercased()) { +// let selector = NSSelectorFromString("\(call.method):") +// if !plugin.responds(to: selector) { +// CAPLog.print("Error: Plugin \(plugin.className ?? "") does not respond to method call \(selector).") +// CAPLog.print("Ensure plugin method exists and uses @objc in its declaration") +// return +// } +// +// let arguments: [Any] = call.options["options"] as? [Any] ?? [] +// let pluginCall = CDVInvokedUrlCommand(arguments: arguments, +// callbackId: call.callbackId, +// className: plugin.className, +// methodName: call.method) +// plugin.perform(selector, with: pluginCall) +// +// } else { +// CAPLog.print("Error: Cordova Plugin mapping not found") +// return +// } +// } /** Send a successful result to the JavaScript layer. diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift index bf542c003..9d9d48d7a 100644 --- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift @@ -180,26 +180,27 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat let pluginId = dict["pluginId"] as? String ?? "" let method = dict["methodName"] as? String ?? "" let callbackId = dict["callbackId"] as? String ?? "" - + let options = dict["options"] as? [String: Any] ?? [:] - + if pluginId != "Console" { CAPLog.print("⚡️ To Native -> ", pluginId, method, callbackId) } - + bridge.handleJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId)) - } else if type == "cordova" { - let pluginId = dict["service"] as? String ?? "" - let method = dict["action"] as? String ?? "" - let callbackId = dict["callbackId"] as? String ?? "" - - let args = dict["actionArgs"] as? Array ?? [] - let options = ["options": args] - - CAPLog.print("To Native Cordova -> ", pluginId, method, callbackId, options) - - bridge.handleCordovaJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId)) } +// } else if type == "cordova" { +// let pluginId = dict["service"] as? String ?? "" +// let method = dict["action"] as? String ?? "" +// let callbackId = dict["callbackId"] as? String ?? "" +// +// let args = dict["actionArgs"] as? Array ?? [] +// let options = ["options": args] +// +// CAPLog.print("To Native Cordova -> ", pluginId, method, callbackId, options) +// +// //bridge.handleCordovaJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId)) +// } } } diff --git a/ios/CapacitorCordova.podspec b/ios/CapacitorCordova.podspec index 478142b1c..dcb711241 100644 --- a/ios/CapacitorCordova.podspec +++ b/ios/CapacitorCordova.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.authors = { 'Ionic Team' => 'hi@ionicframework.com' } s.source = { git: 'https://github.com/ionic-team/capacitor', tag: s.version.to_s } s.platform = :ios, 13.0 - s.source_files = "#{prefix}CapacitorCordova/CapacitorCordova/**/*.{h,m}" + s.source_files = "#{prefix}CapacitorCordova/CapacitorCordova/**/*.{h,m,swift}" s.public_header_files = "#{prefix}CapacitorCordova/CapacitorCordova/Classes/Public/*.h", "#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.h" s.module_map = "#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.modulemap" From cace94e8fbd8a7501a7126e40b989db9e8ac5433 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Mon, 20 May 2024 16:09:55 -0500 Subject: [PATCH 03/10] Continue cordova removal --- .../Capacitor/CAPBridgeProtocol.swift | 3 + .../Capacitor/CAPInstanceDescriptor.swift | 2 +- ios/Capacitor/Capacitor/CapacitorBridge.swift | 68 +++++----- ios/Capacitor/Capacitor/JS.swift | 10 +- ios/Capacitor/Capacitor/JSExport.swift | 76 ++++++------ .../Capacitor/WebViewDelegationHandler.swift | 2 + ios/CapacitorCordova.podspec | 1 + .../Classes/Public/Plugin.swift | 117 ++++++++++++++++++ 8 files changed, 202 insertions(+), 77 deletions(-) create mode 100644 ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift diff --git a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift index 306756e2b..167917df8 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift @@ -77,6 +77,9 @@ import WebKit func registerPluginType(_ pluginType: CAPPlugin.Type) func registerPluginInstance(_ pluginInstance: CAPPlugin) + // MARK: - Interceptors + func registerCallInterceptor(_ name: String, handler: @escaping ([String: Any]) -> Void) + // MARK: - View Presentation func showAlertWith(title: String, message: String, buttonTitle: String) func presentVC(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift index 574794bda..a57a4befe 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift @@ -149,7 +149,7 @@ internal extension InstanceDescriptor { extension InstanceDescriptor { @objc public var cordovaDeployDisabled: Bool { - return true + return false //return (cordovaConfiguration.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false } diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index c100a407a..940244f5d 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -114,11 +114,12 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { // Calls we are storing to resolve later var storedCalls = [String: CAPPluginCall]() // Whether to inject the Cordova files - private var injectCordovaFiles = false + private var cordovaIsPresent = false //private var cordovaParser: CDVConfigParser? // Background dispatch queue for plugin calls open private(set) var dispatchQueue = DispatchQueue(label: "bridge") + internal private(set) var callInterceptors: [String: ([String: Any]) -> Void] = [:] // Array of block based observers var observers: [NSObjectProtocol] = [] @@ -217,7 +218,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { exportCoreJS(localUrl: configuration.localURL.absoluteString) registerPlugins() - // setupCordovaCompatibility() + setupListeners() observers.append(NotificationCenter.default.addObserver(forName: type(of: self).tmpVCAppeared.name, object: .none, queue: .none) { [weak self] _ in self?.tmpWindow = nil }) @@ -249,31 +250,29 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { } } - /** - Set up our Cordova compat by loading all known Cordova plugins and injecting their JS. - */ -// func setupCordovaCompatibility() { -// if injectCordovaFiles { -// exportCordovaJS() -// // registerCordovaPlugins() -// } else { -// observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in -// self?.triggerDocumentJSEvent(eventName: "resume") -// }) -// observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in -// self?.triggerDocumentJSEvent(eventName: "pause") -// }) -// } -// } - - /** - Export the core Cordova JS runtime - */ - func exportCordovaJS() { - do { - try JSExport.exportCordovaJS(userContentController: webViewDelegationHandler.contentController) - } catch { - type(of: self).fatalError(error, error) + // TODO: Change this description + /// Setup listeners if Cordova is not present + func setupListeners() { + if !cordovaIsPresent { + observers.append( + NotificationCenter.default.addObserver( + forName: UIApplication.willEnterForegroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.triggerDocumentJSEvent(eventName: "resume") + } + ) + + observers.append( + NotificationCenter.default.addObserver( + forName: UIApplication.didEnterBackgroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.triggerDocumentJSEvent(eventName: "pause") + } + ) } } @@ -298,12 +297,11 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) for plugin in registrationList.packageClassList { - if let pluginClass = NSClassFromString(plugin) { -// if pluginClass == CDVPlugin.self { -// injectCordovaFiles = true -// } else { - pluginList.append(pluginClass) -// } + if let pluginClass = NSClassFromString(plugin), pluginClass == CAPPlugin.self { + pluginList.append(pluginClass) + if plugin == "CordovaPlugin" { + cordovaIsPresent = true + } } } } @@ -379,6 +377,10 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { // MARK: - CAPBridgeProtocol: Call Management + public func registerCallInterceptor(_ name: String, handler: @escaping ([String: Any]) -> Void) { + callInterceptors[name] = handler + } + @objc public func saveCall(_ call: CAPPluginCall) { storedCalls[call.callbackId] = call } diff --git a/ios/Capacitor/Capacitor/JS.swift b/ios/Capacitor/Capacitor/JS.swift index 46972c48a..b1198b178 100644 --- a/ios/Capacitor/Capacitor/JS.swift +++ b/ios/Capacitor/Capacitor/JS.swift @@ -14,11 +14,11 @@ public typealias JSResultBody = [String: Any] /** * A call originating from JavaScript land */ -internal struct JSCall { - let options: [String: Any] - let pluginId: String - let method: String - let callbackId: String +public struct JSCall { + public let options: [String: Any] + public let pluginId: String + public let method: String + public let callbackId: String } internal protocol JSResultProtocol { diff --git a/ios/Capacitor/Capacitor/JSExport.swift b/ios/Capacitor/Capacitor/JSExport.swift index 2802baa66..e9ed6753f 100644 --- a/ios/Capacitor/Capacitor/JSExport.swift +++ b/ios/Capacitor/Capacitor/JSExport.swift @@ -35,23 +35,23 @@ internal class JSExport { } } - static func exportCordovaJS(userContentController: WKUserContentController) throws { - guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else { - CAPLog.print("ERROR: Required cordova.js file not found. Cordova plugins will not function!") - throw CapacitorBridgeError.errorExportingCoreJS - } - guard let cordovaPluginsUrl = Bundle.main.url(forResource: "public/cordova_plugins", withExtension: "js") else { - CAPLog.print("ERROR: Required cordova_plugins.js file not found. Cordova plugins will not function!") - throw CapacitorBridgeError.errorExportingCoreJS - } - do { - try self.injectFile(fileURL: cordovaUrl, userContentController: userContentController) - try self.injectFile(fileURL: cordovaPluginsUrl, userContentController: userContentController) - } catch { - CAPLog.print("ERROR: Unable to read required cordova files. Cordova plugins will not function!") - throw CapacitorBridgeError.errorExportingCoreJS - } - } +// static func exportCordovaJS(userContentController: WKUserContentController) throws { +// guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else { +// CAPLog.print("ERROR: Required cordova.js file not found. Cordova plugins will not function!") +// throw CapacitorBridgeError.errorExportingCoreJS +// } +// guard let cordovaPluginsUrl = Bundle.main.url(forResource: "public/cordova_plugins", withExtension: "js") else { +// CAPLog.print("ERROR: Required cordova_plugins.js file not found. Cordova plugins will not function!") +// throw CapacitorBridgeError.errorExportingCoreJS +// } +// do { +// try self.injectFile(fileURL: cordovaUrl, userContentController: userContentController) +// try self.injectFile(fileURL: cordovaPluginsUrl, userContentController: userContentController) +// } catch { +// CAPLog.print("ERROR: Unable to read required cordova files. Cordova plugins will not function!") +// throw CapacitorBridgeError.errorExportingCoreJS +// } +// } /** Export the JS required to implement the given plugin. @@ -167,27 +167,27 @@ internal class JSExport { return lines.joined(separator: "\n") } - static func exportCordovaPluginsJS(userContentController: WKUserContentController) throws { - if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) { - self.injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController) - } - } - - static func injectFilesForFolder(folder: URL, userContentController: WKUserContentController) { - let fileManager = FileManager.default - do { - let fileURLs = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: []) - for fileURL in fileURLs { - if fileURL.hasDirectoryPath { - injectFilesForFolder(folder: fileURL, userContentController: userContentController) - } else { - try self.injectFile(fileURL: fileURL, userContentController: userContentController) - } - } - } catch { - CAPLog.print("Error while enumerating files") - } - } +// static func exportCordovaPluginsJS(userContentController: WKUserContentController) throws { +// if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) { +// self.injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController) +// } +// } + +// static func injectFilesForFolder(folder: URL, userContentController: WKUserContentController) { +// let fileManager = FileManager.default +// do { +// let fileURLs = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: []) +// for fileURL in fileURLs { +// if fileURL.hasDirectoryPath { +// injectFilesForFolder(folder: fileURL, userContentController: userContentController) +// } else { +// try self.injectFile(fileURL: fileURL, userContentController: userContentController) +// } +// } +// } catch { +// CAPLog.print("Error while enumerating files") +// } +// } static func injectFile(fileURL: URL, userContentController: WKUserContentController) throws { do { diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift index 9d9d48d7a..183812d3c 100644 --- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift @@ -188,6 +188,8 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat } bridge.handleJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId)) + } else if let handler = bridge.callInterceptors[type] { + handler(dict) } // } else if type == "cordova" { // let pluginId = dict["service"] as? String ?? "" diff --git a/ios/CapacitorCordova.podspec b/ios/CapacitorCordova.podspec index dcb711241..977804818 100644 --- a/ios/CapacitorCordova.podspec +++ b/ios/CapacitorCordova.podspec @@ -22,5 +22,6 @@ Pod::Spec.new do |s| s.module_map = "#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.modulemap" s.resources = ["#{prefix}CapacitorCordova/CapacitorCordova/PrivacyInfo.xcprivacy"] s.requires_arc = true + s.dependency 'Capacitor', s.version.to_s s.framework = 'WebKit' end diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift new file mode 100644 index 000000000..a1ef8c34b --- /dev/null +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift @@ -0,0 +1,117 @@ +import Capacitor + +public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { + public let jsName = "__CordovaPlugin" + public let pluginMethods: [CAPPluginMethod] = [] + public var identifier: String { jsName } + + public override func load() { + injectJavascript() + configureRuntime() + } + + func configureRuntime() { + let parser = CDVConfigParser() + guard let pluginManager = CDVPluginManager( + parser: parser, + viewController: bridge?.viewController, + webView: bridge?.webView + ) else { return } + + + for plugin in parser.startupPluginNames.compactMap({ $0 as? String }) { + _ = pluginManager.getCommandInstance(plugin) + } + + guard let bridge, let webView = bridge.webView else { return } + + exportCordovaPluginsJS(userContentController: webView.configuration.userContentController) + + bridge.registerCallInterceptor("cordova") { [pluginManager] dict in + let pluginId = dict["service"] as? String ?? "" + let method = dict["action"] as? String ?? "" + let callbackId = dict["callbackId"] as? String ?? "" + + let args = dict["actionArgs"] as? Array ?? [] + let options = ["options": args] + + CAPLog.print("To Native Cordova -> ", pluginId, method, callbackId, options) + + if let plugin = pluginManager.getCommandInstance(pluginId.lowercased()) { + let selector = NSSelectorFromString("\(method):") + if !plugin.responds(to: selector) { + CAPLog.print("Error: Plugin \(plugin.className ?? "") does not respond to method call \(selector).") + CAPLog.print("Ensure plugin method exists and uses @objc in its declaration") + return + } + + let arguments = options["options"] ?? [] + let pluginCall = CDVInvokedUrlCommand( + arguments: arguments, + callbackId: callbackId, + className: plugin.className, + methodName: method + ) + + plugin.perform(selector, with: pluginCall) + + } else { + CAPLog.print("Error: Cordova Plugin mapping not found") + return + } + } + + if (parser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false { + // TODO: Ensure that the previously persisted base path will be loaded +// bridge.usePersistedBasePath() + } + } + + + func injectJavascript() { + guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else { + fatalError("ERROR: Required cordova.js file not found. Cordova plugins will not function!") + } + + guard let cordovaPluginsUrl = Bundle.main.url(forResource: "public/cordova_plugins", withExtension: "js") else { + fatalError("ERROR: Required cordova_plugins.js file not found. Cordova plugins will not function!") + } + + guard let webView = bridge?.webView else { return } + + injectFile(fileURL: cordovaUrl, userContentController: webView.configuration.userContentController) + injectFile(fileURL: cordovaPluginsUrl, userContentController: webView.configuration.userContentController) + } + + func exportCordovaPluginsJS(userContentController: WKUserContentController) { + if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) { + self.injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController) + } + } + + func injectFile(fileURL: URL, userContentController: WKUserContentController) { + do { + let data = try String(contentsOf: fileURL, encoding: .utf8) + let userScript = WKUserScript(source: data, injectionTime: .atDocumentStart, forMainFrameOnly: true) + userContentController.addUserScript(userScript) + } catch { + fatalError("Unable to inject js file") + } + } + + func injectFilesForFolder(folder: URL, userContentController: WKUserContentController) { + let fileManager = FileManager.default + do { + let fileURLs = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: []) + for fileURL in fileURLs { + if fileURL.hasDirectoryPath { + injectFilesForFolder(folder: fileURL, userContentController: userContentController) + } else { + injectFile(fileURL: fileURL, userContentController: userContentController) + } + } + } catch { + CAPLog.print("Error while enumerating files") + } + } +} From 1cf85f0f22eeb71d3e3e728a2c240c989cbffda0 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Wed, 22 May 2024 09:56:24 -0500 Subject: [PATCH 04/10] Allow CordovaPlugin to be initialized and used --- ios/Capacitor/Capacitor/CapacitorBridge.swift | 2 +- .../CapacitorCordova/Classes/Public/Plugin.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 940244f5d..a31077260 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -297,7 +297,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) for plugin in registrationList.packageClassList { - if let pluginClass = NSClassFromString(plugin), pluginClass == CAPPlugin.self { + if let pluginClass = NSClassFromString(plugin), pluginClass is CAPPlugin.Type { pluginList.append(pluginClass) if plugin == "CordovaPlugin" { cordovaIsPresent = true diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift index a1ef8c34b..4fbaa357d 100644 --- a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift @@ -1,5 +1,6 @@ import Capacitor +@objc(CordovaPlugin) public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { public let jsName = "__CordovaPlugin" public let pluginMethods: [CAPPluginMethod] = [] @@ -85,7 +86,7 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { func exportCordovaPluginsJS(userContentController: WKUserContentController) { if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) { - self.injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController) + injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController) } } From b9edfe660bd454e24d82cf07a4c9b0a4abd90f61 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Wed, 22 May 2024 12:47:18 -0400 Subject: [PATCH 05/10] Cordova plugins working, first pass --- ios/Capacitor/Capacitor/CapacitorBridge.swift | 6 +++-- .../Classes/Public/Plugin.swift | 23 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index a31077260..a088bc7a1 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -5,7 +5,7 @@ import WebKit internal typealias CapacitorPlugin = CAPPlugin & CAPBridgedPlugin struct RegistrationList: Codable { - let packageClassList: Set + var packageClassList: Set } /** @@ -294,7 +294,9 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { do { if let pluginJSON = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") { let pluginData = try Data(contentsOf: pluginJSON) - let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) + var registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) + #warning("Don't hardcode this") + registrationList.packageClassList.insert("CordovaPlugin") for plugin in registrationList.packageClassList { if let pluginClass = NSClassFromString(plugin), pluginClass is CAPPlugin.Type { diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift index 4fbaa357d..fa19d54cf 100644 --- a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift @@ -12,20 +12,25 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { } func configureRuntime() { - let parser = CDVConfigParser() + guard let configURL = Bundle.main.url(forResource: "config", withExtension: "xml") else { fatalError() } + guard let bridge, let webView = bridge.webView else { return } + + let cordovaConfigParser = CDVConfigParser() + + let xmlParser = XMLParser(contentsOf: configURL) + xmlParser?.delegate = cordovaConfigParser + xmlParser?.parse() + guard let pluginManager = CDVPluginManager( - parser: parser, - viewController: bridge?.viewController, - webView: bridge?.webView + parser: cordovaConfigParser, + viewController: bridge.viewController, + webView: bridge.webView ) else { return } - - for plugin in parser.startupPluginNames.compactMap({ $0 as? String }) { + for plugin in cordovaConfigParser.startupPluginNames.compactMap({ $0 as? String }) { _ = pluginManager.getCommandInstance(plugin) } - guard let bridge, let webView = bridge.webView else { return } - exportCordovaPluginsJS(userContentController: webView.configuration.userContentController) bridge.registerCallInterceptor("cordova") { [pluginManager] dict in @@ -62,7 +67,7 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { } } - if (parser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false { + if (cordovaConfigParser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false { // TODO: Ensure that the previously persisted base path will be loaded // bridge.usePersistedBasePath() } From 17e9374a9f42c2e438dc15a7d99198c798f9e056 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Wed, 22 May 2024 13:03:32 -0400 Subject: [PATCH 06/10] Fixed server base path --- .../Capacitor/CAPBridgeViewController.swift | 15 +++++++++++---- .../Capacitor/CAPInstanceDescriptor.swift | 17 ----------------- ios/Capacitor/Capacitor/CapacitorBridge.swift | 17 ----------------- .../Classes/Public/Plugin.swift | 11 +++++++---- 4 files changed, 18 insertions(+), 42 deletions(-) diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index 9c74c44ef..ce8795e4f 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -49,6 +49,7 @@ import WebKit assetHandler: assetHandler, delegationHandler: delegationHandler) capacitorDidLoad() + updateAppLocationIfNeeded() if configDescriptor.instanceType == .fixed { updateBinaryVersion() @@ -75,18 +76,24 @@ import WebKit - Note: This is called early in the View Controller's lifecycle. Not all properties will be set at invocation. */ open func instanceDescriptor() -> InstanceDescriptor { - let descriptor = InstanceDescriptor.init() - if !isNewBinary && !descriptor.cordovaDeployDisabled { + return InstanceDescriptor() + } + + func updateAppLocationIfNeeded() { + let cordovaPlugin = bridge?.plugin(withName: "CordovaPlugin") + let cordovaDeployDisabled = cordovaPlugin?.perform(Selector(("cordovaDeployDisabled:"))) as? Bool ?? false + + if !isNewBinary && !cordovaDeployDisabled { if let persistedPath = KeyValueStore.standard["serverBasePath", as: String.self], !persistedPath.isEmpty { if let libPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first { - descriptor.appLocation = URL(fileURLWithPath: libPath, isDirectory: true) + let serverBasePath = URL(fileURLWithPath: libPath, isDirectory: true) .appendingPathComponent("NoCloud") .appendingPathComponent("ionic_built_snapshots") .appendingPathComponent(URL(fileURLWithPath: persistedPath, isDirectory: true).lastPathComponent) + setServerBasePath(path: serverBasePath.path) } } } - return descriptor } open func router() -> Router { diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift index a57a4befe..f2b9cdd5a 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift @@ -52,23 +52,6 @@ internal extension InstanceDescriptor { warnings.update(with: .missingFile) } - // parse the cordova configuration -// var configParser: XMLParser? -// if let cordovaURL = cordovaURL, -// FileManager.default.fileExists(atPath: cordovaURL.path, isDirectory: &isDirectory), -// isDirectory.boolValue == false { -// configParser = XMLParser(contentsOf: cordovaURL) -// } else { -// warnings.update(with: .missingCordovaFile) -// // we don't want to break up string literals -// // swiftlint:disable:next line_length -// if let cordovaXML = "".data(using: .utf8) { -// configParser = XMLParser(data: cordovaXML) -// } -// } -// configParser?.delegate = cordovaConfiguration -// configParser?.parse() - // extract our configuration values if let config = config { // to be removed diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index a088bc7a1..d5b4ddf94 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -415,23 +415,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { return self.dispatchQueue } -// func registerCordovaPlugins() { -// guard let cordovaParser = cordovaParser else { -// return -// } -// cordovaPluginManager = CDVPluginManager.init(parser: cordovaParser, viewController: self.viewController, webView: self.getWebView()) -// if cordovaParser.startupPluginNames.count > 0 { -// for pluginName in cordovaParser.startupPluginNames { -// _ = cordovaPluginManager?.getCommandInstance(pluginName as? String) -// } -// } -// do { -// try JSExport.exportCordovaPluginsJS(userContentController: webViewDelegationHandler.contentController) -// } catch { -// type(of: self).fatalError(error, error) -// } -// } - func reload() { self.getWebView()?.reload() } diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift index fa19d54cf..6274f3ef8 100644 --- a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift @@ -6,6 +6,8 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { public let pluginMethods: [CAPPluginMethod] = [] public var identifier: String { jsName } + private var _cordovaDeployDisabled = false + public override func load() { injectJavascript() configureRuntime() @@ -67,12 +69,13 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { } } - if (cordovaConfigParser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false { - // TODO: Ensure that the previously persisted base path will be loaded -// bridge.usePersistedBasePath() - } + _cordovaDeployDisabled = (cordovaConfigParser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false } + @objc + func cordovaDeployDisabled() -> Bool { + return _cordovaDeployDisabled + } func injectJavascript() { guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else { From df8d72db9c97cd85cff64808b416aa9abb470b32 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Wed, 22 May 2024 13:12:46 -0400 Subject: [PATCH 07/10] Update warning text --- ios/Capacitor/Capacitor/CapacitorBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index d5b4ddf94..59487509a 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -295,7 +295,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { if let pluginJSON = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") { let pluginData = try Data(contentsOf: pluginJSON) var registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) - #warning("Don't hardcode this") + #warning("Don't hardcode this, CLI will handle this eventually") registrationList.packageClassList.insert("CordovaPlugin") for plugin in registrationList.packageClassList { From 8798030d145e98adc7cbaf2ef6e81890ad09a2ba Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Wed, 22 May 2024 13:35:40 -0400 Subject: [PATCH 08/10] Fix cordovaDeployDisabled selector nonsense --- ios/Capacitor/Capacitor/CAPBridgeViewController.swift | 7 ++++--- .../CapacitorCordova/Classes/Public/Plugin.swift | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index ce8795e4f..68c015188 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -79,9 +79,10 @@ import WebKit return InstanceDescriptor() } - func updateAppLocationIfNeeded() { - let cordovaPlugin = bridge?.plugin(withName: "CordovaPlugin") - let cordovaDeployDisabled = cordovaPlugin?.perform(Selector(("cordovaDeployDisabled:"))) as? Bool ?? false + /// This function must be called after plugins are loaded or it will have no effect. + open func updateAppLocationIfNeeded() { + let cordovaPlugin = bridge?.plugin(withName: "__CordovaPlugin") + let cordovaDeployDisabled = cordovaPlugin?.perform(Selector(("cordovaDeployDisabled"))).takeUnretainedValue() as? Bool ?? false if !isNewBinary && !cordovaDeployDisabled { if let persistedPath = KeyValueStore.standard["serverBasePath", as: String.self], !persistedPath.isEmpty { diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift index 6274f3ef8..b45b3c461 100644 --- a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift +++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift @@ -73,8 +73,8 @@ public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin { } @objc - func cordovaDeployDisabled() -> Bool { - return _cordovaDeployDisabled + func cordovaDeployDisabled() -> NSNumber { + return _cordovaDeployDisabled as NSNumber } func injectJavascript() { From d7aea92d4d09c9e6713e0617c333eab045a85c21 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Wed, 22 May 2024 14:52:47 -0400 Subject: [PATCH 09/10] Fix Capacitor Project --- ios/Capacitor/Capacitor.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index b1fa36515..a13ff29b2 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ 0F83E885285A332E006C43CB /* AppUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F83E884285A332D006C43CB /* AppUUID.swift */; }; 0F8F33B327DA980A003F49D6 /* PluginConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */; }; - 2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */; }; - 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */; }; 373A69C1255C9360000A6F44 /* NotificationHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */; }; 373A69F2255C95D0000A6F44 /* NotificationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */; }; 501CBAA71FC0A723009B0D4D /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501CBAA61FC0A723009B0D4D /* WebKit.framework */; }; @@ -153,8 +151,6 @@ /* Begin PBXFileReference section */ 0F83E884285A332D006C43CB /* AppUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUUID.swift; sourceTree = ""; }; 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginConfig.swift; sourceTree = ""; }; - 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.h"; sourceTree = ""; }; - 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.m"; sourceTree = ""; }; 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandlerProtocol.swift; sourceTree = ""; }; 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRouter.swift; sourceTree = ""; }; 501CBAA61FC0A723009B0D4D /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; @@ -392,8 +388,6 @@ 62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */, 62D43AEF2581817500673C24 /* WKWebView+Capacitor.swift */, 62D43B642582A13D00673C24 /* WKWebView+Capacitor.m */, - 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */, - 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */, 62959AE92524DA7700A3D7F1 /* UIColor.swift */, 62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */, 62959B122524DA7700A3D7F1 /* Info.plist */, @@ -500,7 +494,6 @@ 623D6909254C6FDF002D01D1 /* CAPInstanceDescriptor.h in Headers */, 62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */, 621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */, - 2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -712,7 +705,6 @@ 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */, 62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */, - 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */, 62ADC0CA25CB678000E914DE /* PluginCallResult.swift in Sources */, 62959B472524DA7800A3D7F1 /* CAPNotifications.swift in Sources */, 62D43B652582A13D00673C24 /* WKWebView+Capacitor.m in Sources */, From ddb8db6593286de258c50b2f70ba0aede4177663 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Thu, 23 May 2024 16:16:48 -0400 Subject: [PATCH 10/10] Move CLI functions to prepare for cordova changes --- cli/src/ios/update.ts | 411 ++--------------------------------- cli/src/util/cordova-ios.ts | 412 ++++++++++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+), 397 deletions(-) create mode 100644 cli/src/util/cordova-ios.ts diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts index 85e0f0c48..a077592f3 100644 --- a/cli/src/ios/update.ts +++ b/cli/src/ios/update.ts @@ -1,11 +1,4 @@ -import { - copy, - remove, - pathExists, - readFile, - realpath, - writeFile, -} from '@ionic/utils-fs'; +import { pathExists, readFile, realpath, writeFile } from '@ionic/utils-fs'; import { basename, dirname, join, relative } from 'path'; import c from '../colors'; @@ -14,15 +7,12 @@ import { checkPluginDependencies, handleCordovaPluginsJS, logCordovaManualSteps, - needsStaticPod, } from '../cordova'; import type { Config } from '../definitions'; import { fatal } from '../errors'; import { logger } from '../log'; import { PluginType, - getAllElements, - getFilePath, getPlatformElement, getPluginType, getPlugins, @@ -30,12 +20,17 @@ import { } from '../plugin'; import type { Plugin } from '../plugin'; import { copy as copyTask } from '../tasks/copy'; +import { + generateCordovaPodspecs, + copyPluginsNativeFiles, + removePluginsNativeFiles, + cordovaPodfileLines, +} from '../util/cordova-ios'; import { convertToUnixPath } from '../util/fs'; import { generateIOSPackageJSON } from '../util/iosplugin'; import { resolveNode } from '../util/node'; import { checkPackageManager, generatePackageFile } from '../util/spm'; import { runCommand, isInstalled } from '../util/subprocess'; -import { extractTemplate } from '../util/template'; import { getIOSPlugins } from './common'; @@ -72,6 +67,9 @@ async function updateIOSCocoaPods( p => getPluginType(p, platform) === PluginType.Cordova, ); if (cordovaPlugins.length > 0) { + // TODO: all the new logic should probably go here + // with an else that removes cordova plugins + await copyPluginsNativeFiles(config, cordovaPlugins); } if (!(await pathExists(await config.ios.webDirAbs))) { @@ -218,380 +216,13 @@ async function generatePodFile( )}'\n`; }), ); - const cordovaPlugins = plugins.filter( - p => getPluginType(p, platform) === PluginType.Cordova, - ); - cordovaPlugins.map(async p => { - const podspecs = getPlatformElement(p, platform, 'podspec'); - podspecs.map((podspec: any) => { - podspec.pods.map((pPods: any) => { - pPods.pod.map((pod: any) => { - if (pod.$.git) { - let gitRef = ''; - if (pod.$.tag) { - gitRef = `, :tag => '${pod.$.tag}'`; - } else if (pod.$.branch) { - gitRef = `, :branch => '${pod.$.branch}'`; - } else if (pod.$.commit) { - gitRef = `, :commit => '${pod.$.commit}'`; - } - pods.push( - ` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`, - ); - } - }); - }); - }); - }); - const staticPlugins = cordovaPlugins.filter(p => needsStaticPod(p, config)); - const noStaticPlugins = cordovaPlugins.filter( - el => !staticPlugins.includes(el), - ); - if (noStaticPlugins.length > 0) { - pods.push( - ` pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'\n`, - ); - } - if (staticPlugins.length > 0) { - pods.push( - ` pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'\n`, - ); - } - const resourcesPlugins = cordovaPlugins.filter(filterResources); - if (resourcesPlugins.length > 0) { - pods.push( - ` pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'\n`, - ); - } + const cordovaPodlines = cordovaPodfileLines(config, plugins); + pods.concat(cordovaPodlines); + return ` pod 'Capacitor', :path => '${relativeCapacitoriOSPath}' pod 'CapacitorCordova', :path => '${relativeCapacitoriOSPath}' -${pods.join('').trimRight()}`; -} - -function getFrameworkName(framework: any) { - if (isFramework(framework)) { - if (framework.$.custom && framework.$.custom === 'true') { - return framework.$.src; - } - return framework.$.src.substr(0, framework.$.src.indexOf('.')); - } - return framework.$.src - .substr(0, framework.$.src.indexOf('.')) - .replace('lib', ''); -} - -function isFramework(framework: any) { - return framework.$.src.split('.').pop().includes('framework'); -} - -async function generateCordovaPodspecs( - cordovaPlugins: Plugin[], - config: Config, -) { - const staticPlugins = cordovaPlugins.filter(p => needsStaticPod(p, config)); - const noStaticPlugins = cordovaPlugins.filter( - el => !staticPlugins.includes(el), - ); - generateCordovaPodspec(noStaticPlugins, config, false); - generateCordovaPodspec(staticPlugins, config, true); -} - -async function generateCordovaPodspec( - cordovaPlugins: Plugin[], - config: Config, - isStatic: boolean, -) { - const weakFrameworks: string[] = []; - const linkedFrameworks: string[] = []; - const customFrameworks: string[] = []; - const systemLibraries: string[] = []; - const sourceFrameworks: string[] = []; - const frameworkDeps: string[] = []; - const compilerFlags: string[] = []; - let prefsArray: any[] = []; - let name = 'CordovaPlugins'; - let sourcesFolderName = 'sources'; - if (isStatic) { - name += 'Static'; - frameworkDeps.push('s.static_framework = true'); - sourcesFolderName += 'static'; - } - cordovaPlugins.map((plugin: any) => { - const frameworks = getPlatformElement(plugin, platform, 'framework'); - frameworks.map((framework: any) => { - if (!framework.$.type) { - const name = getFrameworkName(framework); - if (isFramework(framework)) { - if (framework.$.weak && framework.$.weak === 'true') { - if (!weakFrameworks.includes(name)) { - weakFrameworks.push(name); - } - } else if (framework.$.custom && framework.$.custom === 'true') { - const frameworktPath = join(sourcesFolderName, plugin.name, name); - if (!customFrameworks.includes(frameworktPath)) { - customFrameworks.push(frameworktPath); - } - } else { - if (!linkedFrameworks.includes(name)) { - linkedFrameworks.push(name); - } - } - } else { - if (!systemLibraries.includes(name)) { - systemLibraries.push(name); - } - } - } else if (framework.$.type && framework.$.type === 'podspec') { - let depString = `s.dependency '${framework.$.src}'`; - if (framework.$.spec && framework.$.spec !== '') { - depString += `, '${framework.$.spec}'`; - } - if (!frameworkDeps.includes(depString)) { - frameworkDeps.push(depString); - } - } - }); - prefsArray = prefsArray.concat( - getAllElements(plugin, platform, 'preference'), - ); - const podspecs = getPlatformElement(plugin, platform, 'podspec'); - podspecs.map((podspec: any) => { - podspec.pods.map((pods: any) => { - pods.pod.map((pod: any) => { - let depString = `s.dependency '${pod.$.name}'`; - if (pod.$.spec && pod.$.spec !== '') { - depString += `, '${pod.$.spec}'`; - } - if (!frameworkDeps.includes(depString)) { - frameworkDeps.push(depString); - } - }); - }); - }); - const sourceFiles = getPlatformElement(plugin, platform, 'source-file'); - sourceFiles.map((sourceFile: any) => { - if (sourceFile.$.framework && sourceFile.$.framework === 'true') { - let fileName = sourceFile.$.src.split('/').pop(); - if (!fileName.startsWith('lib')) { - fileName = 'lib' + fileName; - } - const frameworktPath = join(sourcesFolderName, plugin.name, fileName); - if (!sourceFrameworks.includes(frameworktPath)) { - sourceFrameworks.push(frameworktPath); - } - } else if (sourceFile.$['compiler-flags']) { - const cFlag = sourceFile.$['compiler-flags']; - if (!compilerFlags.includes(cFlag)) { - compilerFlags.push(cFlag); - } - } - }); - }); - const onlySystemLibraries = systemLibraries.filter(library => - removeNoSystem(library, sourceFrameworks), - ); - if (weakFrameworks.length > 0) { - frameworkDeps.push(`s.weak_frameworks = '${weakFrameworks.join(`', '`)}'`); - } - if (linkedFrameworks.length > 0) { - frameworkDeps.push(`s.frameworks = '${linkedFrameworks.join(`', '`)}'`); - } - if (onlySystemLibraries.length > 0) { - frameworkDeps.push(`s.libraries = '${onlySystemLibraries.join(`', '`)}'`); - } - if (customFrameworks.length > 0) { - frameworkDeps.push( - `s.vendored_frameworks = '${customFrameworks.join(`', '`)}'`, - ); - frameworkDeps.push( - `s.exclude_files = 'sources/**/*.framework/Headers/*.h', 'sources/**/*.framework/PrivateHeaders/*.h'`, - ); - } - if (sourceFrameworks.length > 0) { - frameworkDeps.push( - `s.vendored_libraries = '${sourceFrameworks.join(`', '`)}'`, - ); - } - if (compilerFlags.length > 0) { - frameworkDeps.push(`s.compiler_flags = '${compilerFlags.join(' ')}'`); - } - const arcPlugins = cordovaPlugins.filter(filterARCFiles); - if (arcPlugins.length > 0) { - frameworkDeps.push(`s.subspec 'noarc' do |sna| - sna.requires_arc = false - sna.source_files = 'noarc/**/*.{swift,h,m,c,cc,mm,cpp}' - end`); - } - let frameworksString = frameworkDeps.join('\n '); - frameworksString = await replaceFrameworkVariables( - config, - prefsArray, - frameworksString, - ); - const content = ` - Pod::Spec.new do |s| - s.name = '${name}' - s.version = '${config.cli.package.version}' - s.summary = 'Autogenerated spec' - s.license = 'Unknown' - s.homepage = 'https://example.com' - s.authors = { 'Capacitor Generator' => 'hi@example.com' } - s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '${ - config.cli.package.version - }' } - s.source_files = '${sourcesFolderName}/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '${config.ios.minVersion}' - s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1 WK_WEB_VIEW_ONLY=1' } - s.dependency 'CapacitorCordova'${getLinkerFlags(config)} - s.swift_version = '5.1' - ${frameworksString} - end`; - await writeFile( - join(config.ios.cordovaPluginsDirAbs, `${name}.podspec`), - content, - ); -} - -function getLinkerFlags(config: Config) { - if (config.app.extConfig.ios?.cordovaLinkerFlags) { - return `\n s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '${config.app.extConfig.ios.cordovaLinkerFlags.join( - ' ', - )}' }`; - } - return ''; -} - -async function copyPluginsNativeFiles( - config: Config, - cordovaPlugins: Plugin[], -) { - for (const p of cordovaPlugins) { - const sourceFiles = getPlatformElement(p, platform, 'source-file'); - const headerFiles = getPlatformElement(p, platform, 'header-file'); - const codeFiles = sourceFiles.concat(headerFiles); - const frameworks = getPlatformElement(p, platform, 'framework'); - let sourcesFolderName = 'sources'; - if (needsStaticPod(p, config)) { - sourcesFolderName += 'static'; - } - const sourcesFolder = join( - config.ios.cordovaPluginsDirAbs, - sourcesFolderName, - p.name, - ); - for (const codeFile of codeFiles) { - let fileName = codeFile.$.src.split('/').pop(); - const fileExt = codeFile.$.src.split('.').pop(); - if (fileExt === 'a' && !fileName.startsWith('lib')) { - fileName = 'lib' + fileName; - } - let destFolder = sourcesFolderName; - if ( - codeFile.$['compiler-flags'] && - codeFile.$['compiler-flags'] === '-fno-objc-arc' - ) { - destFolder = 'noarc'; - } - const filePath = getFilePath(config, p, codeFile.$.src); - const fileDest = join( - config.ios.cordovaPluginsDirAbs, - destFolder, - p.name, - fileName, - ); - await copy(filePath, fileDest); - if (!codeFile.$.framework) { - let fileContent = await readFile(fileDest, { encoding: 'utf-8' }); - if (fileExt === 'swift') { - fileContent = 'import Cordova\n' + fileContent; - await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); - } else { - if (fileContent.includes('@import Firebase;')) { - fileContent = fileContent.replace( - '@import Firebase;', - '#import ', - ); - await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); - } - if ( - fileContent.includes('[NSBundle bundleForClass:[self class]]') || - fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]') - ) { - fileContent = fileContent.replace( - '[NSBundle bundleForClass:[self class]]', - '[NSBundle mainBundle]', - ); - fileContent = fileContent.replace( - '[NSBundle bundleForClass:[CDVCapture class]]', - '[NSBundle mainBundle]', - ); - await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); - } - if ( - fileContent.includes('[self.webView superview]') || - fileContent.includes('self.webView.superview') - ) { - fileContent = fileContent.replace( - /\[self.webView superview\]/g, - 'self.viewController.view', - ); - fileContent = fileContent.replace( - /self.webView.superview/g, - 'self.viewController.view', - ); - await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); - } - } - } - } - const resourceFiles = getPlatformElement(p, platform, 'resource-file'); - for (const resourceFile of resourceFiles) { - const fileName = resourceFile.$.src.split('/').pop(); - await copy( - getFilePath(config, p, resourceFile.$.src), - join(config.ios.cordovaPluginsDirAbs, 'resources', fileName), - ); - } - for (const framework of frameworks) { - if (framework.$.custom && framework.$.custom === 'true') { - await copy( - getFilePath(config, p, framework.$.src), - join(sourcesFolder, framework.$.src), - ); - } - } - } -} - -async function removePluginsNativeFiles(config: Config) { - await remove(config.ios.cordovaPluginsDirAbs); - await extractTemplate( - config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, - config.ios.cordovaPluginsDirAbs, - ); -} - -function filterResources(plugin: Plugin) { - const resources = getPlatformElement(plugin, platform, 'resource-file'); - return resources.length > 0; -} - -function filterARCFiles(plugin: Plugin) { - const sources = getPlatformElement(plugin, platform, 'source-file'); - const sourcesARC = sources.filter( - (sourceFile: any) => - sourceFile.$['compiler-flags'] && - sourceFile.$['compiler-flags'] === '-fno-objc-arc', - ); - return sourcesARC.length > 0; -} - -function removeNoSystem(library: string, sourceFrameworks: string[]) { - const libraries = sourceFrameworks.filter(framework => - framework.includes(library), - ); - return libraries.length === 0; +${pods.join('').trimEnd()}`; } async function getPluginsTask(config: Config) { @@ -601,17 +232,3 @@ async function getPluginsTask(config: Config) { return iosPlugins; }); } - -async function replaceFrameworkVariables( - config: Config, - prefsArray: any[], - frameworkString: string, -) { - prefsArray.map((preference: any) => { - frameworkString = frameworkString.replace( - new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'), - preference.$.default, - ); - }); - return frameworkString; -} diff --git a/cli/src/util/cordova-ios.ts b/cli/src/util/cordova-ios.ts new file mode 100644 index 000000000..d3e71b9f6 --- /dev/null +++ b/cli/src/util/cordova-ios.ts @@ -0,0 +1,412 @@ +import { copy, readFile, writeFile, remove } from '@ionic/utils-fs'; +import { join } from 'path'; + +import { needsStaticPod } from '../cordova'; +import type { Config } from '../definitions'; +import { + PluginType, + getPlatformElement, + getPluginType, + getPlugins, + printPlugins, + getAllElements, + getFilePath, +} from '../plugin'; +import type { Plugin } from '../plugin'; +import { extractTemplate } from '../util/template'; + +const platform = 'ios'; + +export async function generateCordovaPodspecs( + cordovaPlugins: Plugin[], + config: Config, +): Promise { + const staticPlugins = cordovaPlugins.filter(p => needsStaticPod(p, config)); + const noStaticPlugins = cordovaPlugins.filter( + el => !staticPlugins.includes(el), + ); + generateCordovaPodspec(noStaticPlugins, config, false); + generateCordovaPodspec(staticPlugins, config, true); +} + +export async function generateCordovaPodspec( + cordovaPlugins: Plugin[], + config: Config, + isStatic: boolean, +): Promise { + const weakFrameworks: string[] = []; + const linkedFrameworks: string[] = []; + const customFrameworks: string[] = []; + const systemLibraries: string[] = []; + const sourceFrameworks: string[] = []; + const frameworkDeps: string[] = []; + const compilerFlags: string[] = []; + let prefsArray: any[] = []; + let name = 'CordovaPlugins'; + let sourcesFolderName = 'sources'; + if (isStatic) { + name += 'Static'; + frameworkDeps.push('s.static_framework = true'); + sourcesFolderName += 'static'; + } + cordovaPlugins.map((plugin: any) => { + const frameworks = getPlatformElement(plugin, platform, 'framework'); + frameworks.map((framework: any) => { + if (!framework.$.type) { + const name = getFrameworkName(framework); + if (isFramework(framework)) { + if (framework.$.weak && framework.$.weak === 'true') { + if (!weakFrameworks.includes(name)) { + weakFrameworks.push(name); + } + } else if (framework.$.custom && framework.$.custom === 'true') { + const frameworktPath = join(sourcesFolderName, plugin.name, name); + if (!customFrameworks.includes(frameworktPath)) { + customFrameworks.push(frameworktPath); + } + } else { + if (!linkedFrameworks.includes(name)) { + linkedFrameworks.push(name); + } + } + } else { + if (!systemLibraries.includes(name)) { + systemLibraries.push(name); + } + } + } else if (framework.$.type && framework.$.type === 'podspec') { + let depString = `s.dependency '${framework.$.src}'`; + if (framework.$.spec && framework.$.spec !== '') { + depString += `, '${framework.$.spec}'`; + } + if (!frameworkDeps.includes(depString)) { + frameworkDeps.push(depString); + } + } + }); + prefsArray = prefsArray.concat( + getAllElements(plugin, platform, 'preference'), + ); + const podspecs = getPlatformElement(plugin, platform, 'podspec'); + podspecs.map((podspec: any) => { + podspec.pods.map((pods: any) => { + pods.pod.map((pod: any) => { + let depString = `s.dependency '${pod.$.name}'`; + if (pod.$.spec && pod.$.spec !== '') { + depString += `, '${pod.$.spec}'`; + } + if (!frameworkDeps.includes(depString)) { + frameworkDeps.push(depString); + } + }); + }); + }); + const sourceFiles = getPlatformElement(plugin, platform, 'source-file'); + sourceFiles.map((sourceFile: any) => { + if (sourceFile.$.framework && sourceFile.$.framework === 'true') { + let fileName = sourceFile.$.src.split('/').pop(); + if (!fileName.startsWith('lib')) { + fileName = 'lib' + fileName; + } + const frameworktPath = join(sourcesFolderName, plugin.name, fileName); + if (!sourceFrameworks.includes(frameworktPath)) { + sourceFrameworks.push(frameworktPath); + } + } else if (sourceFile.$['compiler-flags']) { + const cFlag = sourceFile.$['compiler-flags']; + if (!compilerFlags.includes(cFlag)) { + compilerFlags.push(cFlag); + } + } + }); + }); + const onlySystemLibraries = systemLibraries.filter(library => + removeNoSystem(library, sourceFrameworks), + ); + if (weakFrameworks.length > 0) { + frameworkDeps.push(`s.weak_frameworks = '${weakFrameworks.join(`', '`)}'`); + } + if (linkedFrameworks.length > 0) { + frameworkDeps.push(`s.frameworks = '${linkedFrameworks.join(`', '`)}'`); + } + if (onlySystemLibraries.length > 0) { + frameworkDeps.push(`s.libraries = '${onlySystemLibraries.join(`', '`)}'`); + } + if (customFrameworks.length > 0) { + frameworkDeps.push( + `s.vendored_frameworks = '${customFrameworks.join(`', '`)}'`, + ); + frameworkDeps.push( + `s.exclude_files = 'sources/**/*.framework/Headers/*.h', 'sources/**/*.framework/PrivateHeaders/*.h'`, + ); + } + if (sourceFrameworks.length > 0) { + frameworkDeps.push( + `s.vendored_libraries = '${sourceFrameworks.join(`', '`)}'`, + ); + } + if (compilerFlags.length > 0) { + frameworkDeps.push(`s.compiler_flags = '${compilerFlags.join(' ')}'`); + } + const arcPlugins = cordovaPlugins.filter(filterARCFiles); + if (arcPlugins.length > 0) { + frameworkDeps.push(`s.subspec 'noarc' do |sna| + sna.requires_arc = false + sna.source_files = 'noarc/**/*.{swift,h,m,c,cc,mm,cpp}' + end`); + } + let frameworksString = frameworkDeps.join('\n '); + frameworksString = await replaceFrameworkVariables( + config, + prefsArray, + frameworksString, + ); + const content = ` + Pod::Spec.new do |s| + s.name = '${name}' + s.version = '${config.cli.package.version}' + s.summary = 'Autogenerated spec' + s.license = 'Unknown' + s.homepage = 'https://example.com' + s.authors = { 'Capacitor Generator' => 'hi@example.com' } + s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '${ + config.cli.package.version + }' } + s.source_files = '${sourcesFolderName}/**/*.{swift,h,m,c,cc,mm,cpp}' + s.ios.deployment_target = '${config.ios.minVersion}' + s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1 WK_WEB_VIEW_ONLY=1' } + s.dependency 'CapacitorCordova'${getLinkerFlags(config)} + s.swift_version = '5.1' + ${frameworksString} + end`; + await writeFile( + join(config.ios.cordovaPluginsDirAbs, `${name}.podspec`), + content, + ); +} + +function getLinkerFlags(config: Config) { + if (config.app.extConfig.ios?.cordovaLinkerFlags) { + return `\n s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '${config.app.extConfig.ios.cordovaLinkerFlags.join( + ' ', + )}' }`; + } + return ''; +} + +export async function copyPluginsNativeFiles( + config: Config, + cordovaPlugins: Plugin[], +): Promise { + for (const p of cordovaPlugins) { + const sourceFiles = getPlatformElement(p, platform, 'source-file'); + const headerFiles = getPlatformElement(p, platform, 'header-file'); + const codeFiles = sourceFiles.concat(headerFiles); + const frameworks = getPlatformElement(p, platform, 'framework'); + let sourcesFolderName = 'sources'; + if (needsStaticPod(p, config)) { + sourcesFolderName += 'static'; + } + const sourcesFolder = join( + config.ios.cordovaPluginsDirAbs, + sourcesFolderName, + p.name, + ); + for (const codeFile of codeFiles) { + let fileName = codeFile.$.src.split('/').pop(); + const fileExt = codeFile.$.src.split('.').pop(); + if (fileExt === 'a' && !fileName.startsWith('lib')) { + fileName = 'lib' + fileName; + } + let destFolder = sourcesFolderName; + if ( + codeFile.$['compiler-flags'] && + codeFile.$['compiler-flags'] === '-fno-objc-arc' + ) { + destFolder = 'noarc'; + } + const filePath = getFilePath(config, p, codeFile.$.src); + const fileDest = join( + config.ios.cordovaPluginsDirAbs, + destFolder, + p.name, + fileName, + ); + await copy(filePath, fileDest); + if (!codeFile.$.framework) { + let fileContent = await readFile(fileDest, { encoding: 'utf-8' }); + if (fileExt === 'swift') { + fileContent = 'import Cordova\n' + fileContent; + await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); + } else { + if (fileContent.includes('@import Firebase;')) { + fileContent = fileContent.replace( + '@import Firebase;', + '#import ', + ); + await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); + } + if ( + fileContent.includes('[NSBundle bundleForClass:[self class]]') || + fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]') + ) { + fileContent = fileContent.replace( + '[NSBundle bundleForClass:[self class]]', + '[NSBundle mainBundle]', + ); + fileContent = fileContent.replace( + '[NSBundle bundleForClass:[CDVCapture class]]', + '[NSBundle mainBundle]', + ); + await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); + } + if ( + fileContent.includes('[self.webView superview]') || + fileContent.includes('self.webView.superview') + ) { + fileContent = fileContent.replace( + /\[self.webView superview\]/g, + 'self.viewController.view', + ); + fileContent = fileContent.replace( + /self.webView.superview/g, + 'self.viewController.view', + ); + await writeFile(fileDest, fileContent, { encoding: 'utf-8' }); + } + } + } + } + const resourceFiles = getPlatformElement(p, platform, 'resource-file'); + for (const resourceFile of resourceFiles) { + const fileName = resourceFile.$.src.split('/').pop(); + await copy( + getFilePath(config, p, resourceFile.$.src), + join(config.ios.cordovaPluginsDirAbs, 'resources', fileName), + ); + } + for (const framework of frameworks) { + if (framework.$.custom && framework.$.custom === 'true') { + await copy( + getFilePath(config, p, framework.$.src), + join(sourcesFolder, framework.$.src), + ); + } + } + } +} + +export async function removePluginsNativeFiles(config: Config): Promise { + await remove(config.ios.cordovaPluginsDirAbs); + await extractTemplate( + config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, + config.ios.cordovaPluginsDirAbs, + ); +} + +export function filterARCFiles(plugin: Plugin): boolean { + const sources = getPlatformElement(plugin, platform, 'source-file'); + const sourcesARC = sources.filter( + (sourceFile: any) => + sourceFile.$['compiler-flags'] && + sourceFile.$['compiler-flags'] === '-fno-objc-arc', + ); + return sourcesARC.length > 0; +} + +function removeNoSystem(library: string, sourceFrameworks: string[]): boolean { + const libraries = sourceFrameworks.filter(framework => + framework.includes(library), + ); + return libraries.length === 0; +} + +async function replaceFrameworkVariables( + config: Config, + prefsArray: any[], + frameworkString: string, +): Promise { + prefsArray.map((preference: any) => { + frameworkString = frameworkString.replace( + new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'), + preference.$.default, + ); + }); + return frameworkString; +} + +function getFrameworkName(framework: any): string { + if (isFramework(framework)) { + if (framework.$.custom && framework.$.custom === 'true') { + return framework.$.src; + } + return framework.$.src.substr(0, framework.$.src.indexOf('.')); + } + return framework.$.src + .substr(0, framework.$.src.indexOf('.')) + .replace('lib', ''); +} + +function isFramework(framework: any) { + return framework.$.src.split('.').pop().includes('framework'); +} + +export function cordovaPodfileLines( + config: Config, + plugins: Plugin[], +): string[] { + const pods: string[] = []; + + const cordovaPlugins = plugins.filter( + p => getPluginType(p, platform) === PluginType.Cordova, + ); + cordovaPlugins.map(async p => { + const podspecs = getPlatformElement(p, platform, 'podspec'); + podspecs.map((podspec: any) => { + podspec.pods.map((pPods: any) => { + pPods.pod.map((pod: any) => { + if (pod.$.git) { + let gitRef = ''; + if (pod.$.tag) { + gitRef = `, :tag => '${pod.$.tag}'`; + } else if (pod.$.branch) { + gitRef = `, :branch => '${pod.$.branch}'`; + } else if (pod.$.commit) { + gitRef = `, :commit => '${pod.$.commit}'`; + } + pods.push( + ` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`, + ); + } + }); + }); + }); + }); + const staticPlugins = cordovaPlugins.filter(p => needsStaticPod(p, config)); + const noStaticPlugins = cordovaPlugins.filter( + el => !staticPlugins.includes(el), + ); + if (noStaticPlugins.length > 0) { + pods.push( + ` pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'\n`, + ); + } + if (staticPlugins.length > 0) { + pods.push( + ` pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'\n`, + ); + } + const resourcesPlugins = cordovaPlugins.filter(filterResources); + if (resourcesPlugins.length > 0) { + pods.push( + ` pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'\n`, + ); + } + + return pods; +} + +function filterResources(plugin: Plugin) { + const resources = getPlatformElement(plugin, platform, 'resource-file'); + return resources.length > 0; +}