diff --git a/.github/workflows/capacitor-bot.yml b/.github/workflows-disabled/capacitor-bot.yml similarity index 100% rename from .github/workflows/capacitor-bot.yml rename to .github/workflows-disabled/capacitor-bot.yml diff --git a/.github/workflows/ci.yml b/.github/workflows-disabled/ci.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows-disabled/ci.yml diff --git a/.github/workflows/needs-reply.yml b/.github/workflows-disabled/needs-reply.yml similarity index 100% rename from .github/workflows/needs-reply.yml rename to .github/workflows-disabled/needs-reply.yml diff --git a/README.md b/README.md index e76f0dfd..1b3c3ca4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,91 @@ +# Deprecation Notice + +This package is deprecated in favor of https://ionic.io/portals. +"Portals" provides a better dev-experience and is actively supported by Ionic and newer Capacitor-versions. +__________ + +# Embedded Capacitor + +This project enables an "embedded usage" of https://capacitorjs.com/ within existing native apps. +It exists because of the following frustrating situation: + +- Capacitor 2.X provides only bad support for "embedded usage", but promised to deliver with Capacitor 3.X [[3405](https://github.com/ionic-team/capacitor/pull/3405)] +- Capacitor 3.X deliberately destroyed "embedded usage", but promised to deliver an embedded closed-source-solution [[4343](https://github.com/ionic-team/capacitor/issues/4343), [4370](https://github.com/ionic-team/capacitor/issues/4370)] + +For the time being, I consider Capacitor 2.X as more stable for embedded usage. +Therefore, this project only works with Capacitor 2.X. + +## Improvements over Ionic's Capacitor + +With only minimal changes, this project provides the following improvements over Ionic's Capacitor 2.X: + +- Configure custom URL-paths for Android/iOS: [3405](https://github.com/ionic-team/capacitor/pull/3405), [3106](https://github.com/ionic-team/capacitor/issues/3106) +- Make iOS `CAPBridgeViewController` extensible to better support embedded usage: [1972](https://github.com/ionic-team/capacitor/pull/1972) +- Fix Android-crashes related to unneeded plugins: [4379](https://github.com/ionic-team/capacitor/issues/4379) +- Disable splashscreen-plugin by default to speedup launches (you can still enable it). + +To convince yourself, here are the differences between this project and Ionic's Capacitor: https://github.com/fkirc/embedded-capacitor/pull/1/files + +## Installation + +Before you install this project, ensure that regular Capacitor 2.X is working with your project (e.g. it should work in "fullscreen-mode"). +Once you finished a regular Capacitor 2.X setup, replace your `@capacitor/android` or `@capacitor/ios` packages as follows: + +`npm uninstall @capacitor/android` +`npm install capacitor-embedded-android` + +`npm uninstall @capacitor/ios` +`npm install capacitor-embedded-ios` + +Next, create symlinks from the original package-locations to the new package-locations: + +`ln -s "$PWD/node_modules/capacitor-embedded-android/" node_modules/@capacitor/android` +`ln -s "$PWD/node_modules/capacitor-embedded-ios/" node_modules/@capacitor/ios` + +Those symlinks need to be created every time when `node_modules` is created. +Therefore, I recommend adding a `postinstall`-script to your `package.json`: + +```` + "scripts": { + "postinstall": "ln -s \"$PWD/node_modules/capacitor-embedded-android/\" node_modules/@capacitor/android && ln -s \"$PWD/node_modules/capacitor-embedded-ios/\" node_modules/@capacitor/ios" + }, +```` + +Optionally, I recommend the [capsafe](https://github.com/fkirc/capacitor-build-safety) tool to increase safety and traceability of your Capacitor-apps. + +Finally, follow the Android/iOS-specific instructions below. + +### Embedded Android + +For Android, I recommend to subclass `BridgeFragment` for embedded usage: + +````Kotlin +import android.os.Bundle +import com.getcapacitor.BridgeFragment + +class MyBridgeFragment : BridgeFragment() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + super.setUrlPath("/#embedded_feature_1") // Set an URL-path for your embedded usage + } +} +```` + +### Embedded iOS + +For iOS, I recommend to subclass `CAPBridgeViewController` for embedded usage: + +````Swift +public class MyCAPBridgeViewController: CAPBridgeViewController { + public override func loadView() { + super.setUrlPath(path: "/#embedded_feature_1") // Set an URL-path for your embedded usage + super.loadView() + } +} +```` + +--- +


diff --git a/android/capacitor/src/main/AndroidManifest.xml b/android/capacitor/src/main/AndroidManifest.xml index cf142882..94585f59 100644 --- a/android/capacitor/src/main/AndroidManifest.xml +++ b/android/capacitor/src/main/AndroidManifest.xml @@ -1,28 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index f6c257de..8888aaea 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -136,7 +136,7 @@ public class Bridge { * @param context * @param webView */ - public Bridge(Activity context, WebView webView, List> initialPlugins, CordovaInterfaceImpl cordovaInterface, PluginManager pluginManager, CordovaPreferences preferences, JSONObject config) { + public Bridge(Activity context, WebView webView, List> initialPlugins, CordovaInterfaceImpl cordovaInterface, PluginManager pluginManager, CordovaPreferences preferences, JSONObject config, String urlPath) { this.context = context; this.webView = webView; this.webViewClient = new BridgeWebViewClient(this); @@ -169,10 +169,10 @@ public Bridge(Activity context, WebView webView, List> i // Register our core plugins this.registerAllPlugins(); - this.loadWebView(); + this.loadWebView(urlPath); } - private void loadWebView() { + private void loadWebView(String urlPath) { appUrlConfig = this.getServerUrl(); String[] appAllowNavigationConfig = this.config.getArray("server.allowNavigation"); @@ -207,6 +207,9 @@ private void loadWebView() { appUrl += "/"; } } + if (urlPath != null) { + appUrl += urlPath; + } final boolean html5mode = this.config.getBoolean("server.html5mode", true); @@ -430,7 +433,7 @@ private void registerAllPlugins() { this.registerPlugin(Photos.class); this.registerPlugin(PushNotifications.class); this.registerPlugin(Share.class); - this.registerPlugin(SplashScreen.class); + //this.registerPlugin(SplashScreen.class); this.registerPlugin(StatusBar.class); this.registerPlugin(Storage.class); this.registerPlugin(com.getcapacitor.plugin.Toast.class); diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java index dad3dfb7..196fc81e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java @@ -80,7 +80,7 @@ protected void load(Bundle savedInstanceState) { pluginManager = mockWebView.getPluginManager(); cordovaInterface.onCordovaInit(pluginManager); - bridge = new Bridge(this, webView, initialPlugins, cordovaInterface, pluginManager, preferences, this.config); + bridge = new Bridge(this, webView, initialPlugins, cordovaInterface, pluginManager, preferences, this.config, null); if (savedInstanceState != null) { bridge.restoreInstanceState(savedInstanceState); diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java index a527a7a5..70bff3c1 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java @@ -45,6 +45,11 @@ public BridgeFragment() { // Required empty public constructor } + private String urlPath; + public void setUrlPath(String urlPath) { + this.urlPath = urlPath; + } + /** * Use this factory method to create a new instance of * this fragment using the provided parameters. @@ -72,7 +77,7 @@ public void addPlugin(Class plugin) { * Load the WebView and create the Bridge */ protected void load(Bundle savedInstanceState) { - Logger.debug("Starting BridgeActivity"); + Logger.debug("Starting BridgeFragment"); Bundle args = getArguments(); String startDir = null; @@ -97,7 +102,7 @@ protected void load(Bundle savedInstanceState) { preferences = new CordovaPreferences(); } - bridge = new Bridge(this.getActivity(), webView, initialPlugins, cordovaInterface, pluginManager, preferences, config); + bridge = new Bridge(this.getActivity(), webView, initialPlugins, cordovaInterface, pluginManager, preferences, config, urlPath); if (startDir != null) { bridge.setServerAssetPath(startDir); diff --git a/android/package.json b/android/package.json index 15167fab..735b9def 100644 --- a/android/package.json +++ b/android/package.json @@ -1,6 +1,6 @@ { - "name": "@capacitor/android", - "version": "2.4.7", + "name": "capacitor-embedded-android", + "version": "2.4.7-1", "description": "Capacitor: cross-platform mobile apps with the web", "homepage": "https://capacitor.ionicframework.com/", "author": "Ionic Team (https://ionicframework.com) ", diff --git a/capacitor-cordova-android-plugins/build.gradle b/capacitor-cordova-android-plugins/build.gradle index 9d81326d..7d72ca94 100644 --- a/capacitor-cordova-android-plugins/build.gradle +++ b/capacitor-cordova-android-plugins/build.gradle @@ -18,7 +18,7 @@ android { compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 29 defaultConfig { minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 21 - targetSdkVersion targetSdkVersion = project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 29 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 29 versionCode 1 versionName "1.0" } diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index 95abcb00..1aa2f503 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -7,7 +7,7 @@ import UIKit import WebKit import Cordova -public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate { +open class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate { private var webView: WKWebView? @@ -42,8 +42,13 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr // Construct the Capacitor runtime public var bridge: CAPBridge? private var handler: CAPAssetHandler? - - override public func loadView() { + + private var urlPath: String? + open func setUrlPath(path: String) { + self.urlPath = path + } + + override open func loadView() { let configUrl = Bundle.main.url(forResource: "config", withExtension: "xml") let configParser = XMLParser(contentsOf: configUrl!)!; configParser.delegate = cordovaParser @@ -164,7 +169,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr return false } - override public func viewDidLoad() { + override open func viewDidLoad() { super.viewDidLoad() self.becomeFirstResponder() loadWebView() @@ -202,6 +207,9 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr hostname = bridge!.config.getString("server.url") ?? "\(bridge!.getLocalUrl())" allowNavigationConfig = bridge!.config.getValue("server.allowNavigation") as? Array + if let urlPath = self.urlPath { + hostname! += urlPath + } if bridge!.isDevMode() && bridge!.config.getString("server.url") != nil { let toastPlugin = bridge!.getOrLoadPlugin(pluginName: "Toast") as? CAPToastPlugin @@ -218,7 +226,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr self.handler?.setAssetPath(path) } - public func setStatusBarDefaults() { + open func setStatusBarDefaults() { if let plist = Bundle.main.infoDictionary { if let statusBarHidden = plist["UIStatusBarHidden"] as? Bool { if (statusBarHidden) { @@ -239,7 +247,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr } } - public func setScreenOrientationDefaults() { + open func setScreenOrientationDefaults() { if let plist = Bundle.main.infoDictionary { if let orientations = plist["UISupportedInterfaceOrientations"] as? Array { for orientation in orientations { @@ -263,19 +271,19 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr } } - public func configureWebView(configuration: WKWebViewConfiguration) { + open func configureWebView(configuration: WKWebViewConfiguration) { configuration.allowsInlineMediaPlayback = true configuration.suppressesIncrementalRendering = false configuration.allowsAirPlayForMediaPlayback = true configuration.mediaTypesRequiringUserActionForPlayback = [] } - public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + open func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { // Reset the bridge on each navigation bridge!.reset() } - public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + open func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { NotificationCenter.default.post(name: Notification.Name(CAPNotifications.DecidePolicyForNavigationAction.name()), object: navigationAction) let navUrl = navigationAction.request.url! @@ -321,7 +329,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr decisionHandler(.allow) } - public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { if case .initialLoad(let isOpaque) = webViewLoadingState { webView.isOpaque = isOpaque webViewLoadingState = .subsequentLoad @@ -329,7 +337,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr CAPLog.print("⚡️ WebView loaded") } - public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + open func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { if case .initialLoad(let isOpaque) = webViewLoadingState { webView.isOpaque = isOpaque webViewLoadingState = .subsequentLoad @@ -338,20 +346,20 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr CAPLog.print("⚡️ Error: " + error.localizedDescription) } - public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + open func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { CAPLog.print("⚡️ WebView failed provisional navigation") CAPLog.print("⚡️ Error: " + error.localizedDescription) } - public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { + open func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { webView.reload() } - public override func canPerformUnwindSegueAction(_ action: Selector, from fromViewController: UIViewController, withSender sender: Any) -> Bool { + open override func canPerformUnwindSegueAction(_ action: Selector, from fromViewController: UIViewController, withSender sender: Any) -> Bool { return false } - public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + open func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let bridge = bridge else { return } @@ -448,48 +456,48 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr return host == pattern } - override public func didReceiveMemoryWarning() { + override open func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - override public var prefersStatusBarHidden: Bool { + override open var prefersStatusBarHidden: Bool { get { return !isStatusBarVisible } } - override public var preferredStatusBarStyle: UIStatusBarStyle { + override open var preferredStatusBarStyle: UIStatusBarStyle { get { return statusBarStyle } } - override public var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + override open var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { get { return statusBarAnimation } } - public func setStatusBarVisible(_ isStatusBarVisible: Bool) { + open func setStatusBarVisible(_ isStatusBarVisible: Bool) { self.isStatusBarVisible = isStatusBarVisible UIView.animate(withDuration: 0.2, animations: { self.setNeedsStatusBarAppearanceUpdate() }) } - public func setStatusBarStyle(_ statusBarStyle: UIStatusBarStyle) { + open func setStatusBarStyle(_ statusBarStyle: UIStatusBarStyle) { self.statusBarStyle = statusBarStyle UIView.animate(withDuration: 0.2, animations: { self.setNeedsStatusBarAppearanceUpdate() }) } - public func setStatusBarAnimation(_ statusBarAnimation: UIStatusBarAnimation) { + open func setStatusBarAnimation(_ statusBarAnimation: UIStatusBarAnimation) { self.statusBarAnimation = statusBarAnimation } - public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + open func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) @@ -500,7 +508,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr self.present(alertController, animated: true, completion: nil) } - public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { + open func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) @@ -515,7 +523,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr self.present(alertController, animated: true, completion: nil) } - public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { + open func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .alert) @@ -541,22 +549,22 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr self.present(alertController, animated: true, completion: nil) } - public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + open func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if (navigationAction.request.url != nil) { UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil) } return nil } - public func getWebView() -> WKWebView { + open func getWebView() -> WKWebView { return self.webView! } - public func getServerBasePath() -> String { + open func getServerBasePath() -> String { return self.basePath } - public func setServerBasePath(path: String) { + open func setServerBasePath(path: String) { setServerPath(path: path) let request = URLRequest(url: URL(string: hostname!)!) DispatchQueue.main.async { @@ -564,7 +572,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKScr } } - override public var supportedInterfaceOrientations: UIInterfaceOrientationMask { + override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { var ret = 0 if self.supportedOrientations.contains(UIInterfaceOrientation.portrait.rawValue) { ret = ret | (1 << UIInterfaceOrientation.portrait.rawValue) diff --git a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m index b6d36d6f..31c32184 100644 --- a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m +++ b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m @@ -139,10 +139,10 @@ CAP_PLUGIN_METHOD(share, CAPPluginReturnPromise); ) -CAP_PLUGIN(CAPSplashScreenPlugin, "SplashScreen", +/*CAP_PLUGIN(CAPSplashScreenPlugin, "SplashScreen", CAP_PLUGIN_METHOD(show, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(hide, CAPPluginReturnPromise); -) +)*/ CAP_PLUGIN(CAPStatusBarPlugin, "StatusBar", CAP_PLUGIN_METHOD(setStyle, CAPPluginReturnPromise); diff --git a/ios/package.json b/ios/package.json index 1f30986b..2d568b3e 100644 --- a/ios/package.json +++ b/ios/package.json @@ -1,6 +1,6 @@ { - "name": "@capacitor/ios", - "version": "2.4.7", + "name": "capacitor-embedded-ios", + "version": "2.4.7-1", "description": "Capacitor: cross-platform mobile apps with the web", "homepage": "https://capacitor.ionicframework.com/", "author": "Ionic Team (https://ionicframework.com) ",