Skip to content

Commit

Permalink
feat(ios): Plugin Registration and Plugin Instance Support (#6072)
Browse files Browse the repository at this point in the history
* feat(ios): Adds support for manual registration for plugins and the ability to register instances of plugins so that plugins can be instantiated by the user instead of relying on the bridge.
  • Loading branch information
Steven0351 committed Nov 22, 2022
1 parent 3d5b7c2 commit 9f1d863
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 22 deletions.
8 changes: 8 additions & 0 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
A38C3D7B2848BE6F004B3680 /* CapacitorCookieManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38C3D7A2848BE6F004B3680 /* CapacitorCookieManager.swift */; };
A71289E627F380A500DADDF3 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289E527F380A500DADDF3 /* Router.swift */; };
A71289EB27F380FD00DADDF3 /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289EA27F380FD00DADDF3 /* RouterTests.swift */; };
A7F7EDCD291EC75C0015B73B /* CAPPlugin+LoadInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */; };
A7F7EDD5292BE8520015B73B /* CAPInstancePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F7EDD4292BE8520015B73B /* CAPInstancePlugin.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -225,6 +227,8 @@
A38C3D7A2848BE6F004B3680 /* CapacitorCookieManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorCookieManager.swift; sourceTree = "<group>"; };
A71289E527F380A500DADDF3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
A71289EA27F380FD00DADDF3 /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = "<group>"; };
A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAPPlugin+LoadInstance.swift"; sourceTree = "<group>"; };
A7F7EDD4292BE8520015B73B /* CAPInstancePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPInstancePlugin.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -312,12 +316,14 @@
62959B0F2524DA7700A3D7F1 /* Capacitor.h */,
62959B132524DA7700A3D7F1 /* CAPPlugin.h */,
62959B012524DA7700A3D7F1 /* CAPPlugin.m */,
A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */,
62959AE52524DA7700A3D7F1 /* CAPBridgedPlugin.h */,
62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */,
62959AE82524DA7700A3D7F1 /* CAPPluginMethod.m */,
62959AE22524DA7700A3D7F1 /* CAPPluginCall.h */,
62959B062524DA7700A3D7F1 /* CAPPluginCall.m */,
62959AE62524DA7700A3D7F1 /* CAPPluginCall.swift */,
A7F7EDD4292BE8520015B73B /* CAPInstancePlugin.swift */,
62ADC0C925CB678000E914DE /* PluginCallResult.swift */,
621ECCBB2542046400D3D615 /* JSTypes.swift */,
621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */,
Expand Down Expand Up @@ -612,6 +618,7 @@
621ECCDA254205C400D3D615 /* CapacitorBridge.swift in Sources */,
62959B382524DA7800A3D7F1 /* CAPPluginCall.m in Sources */,
623D690A254C6FDF002D01D1 /* CAPInstanceDescriptor.m in Sources */,
A7F7EDD5292BE8520015B73B /* CAPInstancePlugin.swift in Sources */,
A38C3D7B2848BE6F004B3680 /* CapacitorCookieManager.swift in Sources */,
62959B1B2524DA7800A3D7F1 /* CAPFile.swift in Sources */,
62959B462524DA7800A3D7F1 /* CAPBridge.swift in Sources */,
Expand Down Expand Up @@ -645,6 +652,7 @@
6214934725509C3F006C36F9 /* CAPInstanceConfiguration.swift in Sources */,
623D6914254C7030002D01D1 /* CAPInstanceDescriptor.swift in Sources */,
621ECCE3254206A600D3D615 /* CAPApplicationDelegateProxy.swift in Sources */,
A7F7EDCD291EC75C0015B73B /* CAPPlugin+LoadInstance.swift in Sources */,
62959B262524DA7800A3D7F1 /* WebView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
5 changes: 5 additions & 0 deletions ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import WebKit
var isSimEnvironment: Bool { get }
var isDevEnvironment: Bool { get }
var userInterfaceStyle: UIUserInterfaceStyle { get }
var autoRegisterPlugins: Bool { get }
var statusBarVisible: Bool { get set }
var statusBarStyle: UIStatusBarStyle { get set }
var statusBarAnimation: UIStatusBarAnimation { get set }
Expand Down Expand Up @@ -72,6 +73,10 @@ import WebKit
func portablePath(fromLocalURL localURL: URL?) -> URL?
func setServerBasePath(_ path: String)

// MARK: - Plugins
func registerPluginType(_ pluginType: CAPPlugin.Type)
func registerPluginInstance(_ pluginInstance: CAPPlugin)

// MARK: - View Presentation
func showAlertWith(title: String, message: String, buttonTitle: String)
func presentVC(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
Expand Down
10 changes: 10 additions & 0 deletions ios/Capacitor/Capacitor/CAPInstancePlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// CAPInstancePlugin.swift
// Capacitor
//
// Created by Steven Sherry on 11/21/22.
// Copyright © 2022 Drifty Co. All rights reserved.
//

/// A CAPPlugin subclass meant to be explicitly initialized by the caller and not the bridge.
@objc open class CAPInstancePlugin: CAPPlugin {}
19 changes: 19 additions & 0 deletions ios/Capacitor/Capacitor/CAPPlugin+LoadInstance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// CAPPlugin+LoadInstance.swift
// Capacitor
//
// Created by Steven Sherry on 11/9/22.
// Copyright © 2022 Drifty Co. All rights reserved.
//

extension CAPPlugin {
func load(as bridgedType: CAPBridgedPlugin.Type, on bridge: CAPBridgeProtocol) {
self.bridge = bridge
webView = bridge.webView
pluginId = bridgedType.pluginId()
pluginName = bridgedType.jsName()
shouldStringifyDatesInCalls = true
retainedEventArguments = [:]
load()
}
}
8 changes: 6 additions & 2 deletions ios/Capacitor/Capacitor/CAPWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ open class CAPWebView: UIView {
delegate: self,
cordovaConfiguration: configDescriptor.cordovaConfiguration,
assetHandler: assetHandler,
delegationHandler: delegationHandler
delegationHandler: delegationHandler,
autoRegisterPlugins: autoRegisterPlugins
)

public final var bridge: CAPBridgeProtocol {
Expand All @@ -31,15 +32,18 @@ open class CAPWebView: UIView {
}()

private lazy var delegationHandler = WebViewDelegationHandler()
private let autoRegisterPlugins: Bool

open var router: Router { _Router() }

public required init?(coder: NSCoder) {
autoRegisterPlugins = true
super.init(coder: coder)
setup()
}

public init() {
public init(autoRegisterPlugins: Bool = true) {
self.autoRegisterPlugins = autoRegisterPlugins
super.init(frame: .zero)
setup()
}
Expand Down
83 changes: 63 additions & 20 deletions ios/Capacitor/Capacitor/CapacitorBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {
return bridgeDelegate?.bridgedWebView
}

public let autoRegisterPlugins: Bool

public var notificationRouter: NotificationRouter

public var isSimEnvironment: Bool {
Expand Down Expand Up @@ -188,15 +190,15 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {

// MARK: - Initialization

init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler) {
init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, 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
super.init()

self.webViewDelegationHandler.bridge = self
Expand Down Expand Up @@ -273,32 +275,73 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {
Register all plugins that have been declared
*/
func registerPlugins() {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CAPPlugin.Type,
let bridgeType = aClass as? CAPBridgedPlugin.Type {
let pluginClassName = NSStringFromClass(aClass)
registerPlugin(pluginClassName, bridgeType.jsName(), pluginType)
if autoRegisterPlugins {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CAPPlugin.Type,
let bridgeType = aClass as? CAPBridgedPlugin.Type {
if aClass is CAPInstancePlugin.Type { continue }
registerPlugin(bridgeType.jsName(), pluginType)
}
}
}
classes.deallocate()
} else {
// register core plugins only
[CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]
.forEach { registerPluginType($0) }
}
}

public func registerPluginType(_ pluginType: CAPPlugin.Type) {
if autoRegisterPlugins { return }
if pluginType is CAPInstancePlugin.Type {
Swift.fatalError("""
⚡️ ❌ Cannot register class \(pluginType): CAPInstancePlugin through registerPluginType(_:).
⚡️ ❌ Use `registerPluginInstance(_:)` to register subclasses of CAPInstancePlugin.
""")
}
guard let bridgedType = pluginType as? CAPBridgedPlugin.Type else { return }
registerPlugin(bridgedType.jsName(), pluginType)
}

public func registerPluginInstance(_ pluginInstance: CAPPlugin) {
guard
let pluginInstance = pluginInstance as? (CAPPlugin & CAPBridgedPlugin),
let pluginClass = pluginInstance.classForCoder as? (CAPPlugin & CAPBridgedPlugin).Type
else { return }

let jsName = pluginClass.jsName()!

knownPlugins[jsName] = pluginClass
if plugins[jsName] != nil {
CAPLog.print("⚡️ Overriding existing registered plugin \(pluginClass)")
}
classes.deallocate()
plugins[jsName] = pluginInstance
pluginInstance.load(as: pluginClass, on: self)

JSExport.exportJS(
userContentController: webViewDelegationHandler.contentController,
pluginClassName: jsName,
pluginType: pluginClass
)
}

/**
Register a single plugin.
*/
func registerPlugin(_ pluginClassName: String, _ jsName: String, _ pluginType: CAPPlugin.Type) {
func registerPlugin(_ jsName: String, _ pluginType: CAPPlugin.Type) {
// let bridgeType = pluginType as! CAPBridgedPlugin.Type
knownPlugins[jsName] = pluginType
JSExport.exportJS(userContentController: webViewDelegationHandler.contentController, pluginClassName: jsName, pluginType: pluginType)
Expand Down

0 comments on commit 9f1d863

Please sign in to comment.