Skip to content

Commit

Permalink
Add new WebView asset handling and bridge functionality
Browse files Browse the repository at this point in the history
- Introduced `WebViewAssetHandler` for handling custom URL schemes in WKWebView.
- Added `CapacitorBridge` class to manage plugin registration and JavaScript communication.
- Implemented `JSValueEncoder` for encoding Swift objects to JavaScript values.
- Created `CAPBridgeViewController` to manage the lifecycle and configuration of the web view.
- Added `WebViewDelegationHandler` for handling web view navigation and script messages.
- Introduced `KeyValueStore` for managing key-value storage with support for ephemeral and persistent backends.
- Added `ApplicationDelegateProxy` to handle URL opening and universal links.
- Updated various plugins and utilities to integrate with the new bridge and asset handling system.
- Enhanced logging and error handling for better debugging and diagnostics.
  • Loading branch information
tachibana-shin committed Aug 7, 2024
1 parent 53ff0cc commit 21ed86f
Show file tree
Hide file tree
Showing 116 changed files with 9,983 additions and 15 deletions.
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ src-capacitor/ios/App/App/public/plugins/cordova-plugin-screen-orientation/www/s
@capacitor/android/capacitor/src/main/java/com/getcapacitor/annotation/* linguist-vendored
@capacitor/android/capacitor/src/main/java/com/getcapacitor/cordova/* linguist-vendored
@capacitor/android/capacitor/src/main/java/com/getcapacitor/plugin/* linguist-vendored
@capacitor/android/capacitor/src/main/java/com/getcapacitor/util/* linguist-vendored
@capacitor/android/capacitor/src/main/java/com/getcapacitor/util/* linguist-vendored

native-bridge.js linguist-vendored
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"vue.volar",
"wayou.vscode-todo-highlight"
"wayou.vscode-todo-highlight",
"lokalise.i18n-ally"
],
"unwantedRecommendations": [
"octref.vetur",
Expand Down
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,13 @@
"MutableAI.upsell": true,
"[java]": {
"editor.defaultFormatter": "redhat.java"
},
"[swift]": {
"editor.defaultFormatter": "vknabel.vscode-apple-swift-format"
},
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off"
}
}
24 changes: 24 additions & 0 deletions @capacitor/ios/Capacitor.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
prefix = if ENV['NATIVE_PUBLISH'] == 'true'
'ios/'
else
''
end

Pod::Spec.new do |s|
s.name = 'Capacitor'
s.version = package['version']
s.summary = 'Capacitor for iOS'
s.social_media_url = 'https://twitter.com/capacitorjs'
s.license = 'MIT'
s.homepage = 'https://capacitorjs.com/'
s.ios.deployment_target = '13.0'
s.authors = { 'Ionic Team' => '[email protected]' }
s.source = { git: 'https://github.com/ionic-team/capacitor.git', tag: package['version'] }
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
51 changes: 51 additions & 0 deletions @capacitor/ios/Capacitor/Capacitor/AppUUID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import CommonCrypto
import Foundation

private func hexString(_ iterator: Array<UInt8>.Iterator) -> String {
return iterator.map { String(format: "%02x", $0) }.joined()
}

extension Data {
public var sha256: String {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes { bytes in
_ = CC_SHA256(bytes.baseAddress, CC_LONG(self.count), &digest)
}
return hexString(digest.makeIterator())
}
}

public class AppUUID {
private static let key: String = "CapacitorAppUUID"

public static func getAppUUID() -> String {
assertAppUUID()
return readUUID()
}

public static func regenerateAppUUID() {
let uuid = generateUUID()
writeUUID(uuid)
}

private static func assertAppUUID() {
let uuid = readUUID()
if uuid == "" {
regenerateAppUUID()
}
}

private static func generateUUID() -> String {
let uuid: String = UUID.init().uuidString
return uuid.data(using: .utf8)!.sha256
}

private static func readUUID() -> String {
KeyValueStore.standard[key] ?? ""
}

private static func writeUUID(_ uuid: String) {
KeyValueStore.standard[key] = uuid
}

}
31 changes: 31 additions & 0 deletions @capacitor/ios/Capacitor/Capacitor/Array+Capacitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// convenience wrappers to transform Arrays between NSNull and Optional values, for interoperability with Obj-C
extension Array: CapacitorExtension {}
extension CapacitorExtensionTypeWrapper where T == [JSValue] {
public func replacingNullValues() -> [JSValue?] {
return baseType.map({ (value) -> JSValue? in
if value is NSNull {
return nil
}
return value
})
}

public func replacingOptionalValues() -> [JSValue] {
return baseType
}
}

extension CapacitorExtensionTypeWrapper where T == [JSValue?] {
public func replacingNullValues() -> [JSValue?] {
return baseType
}

public func replacingOptionalValues() -> [JSValue] {
return baseType.map({ (value) -> JSValue in
if let value = value {
return value
}
return NSNull()
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

@objc(CAPApplicationDelegateProxy)
public class ApplicationDelegateProxy: NSObject, UIApplicationDelegate {
public static let shared = ApplicationDelegateProxy()

public private(set) var lastURL: URL?

public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
NotificationCenter.default.post(name: .capacitorOpenURL, object: [
"url": url,
"options": options
])
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: url)
lastURL = url
return true
}

public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// TODO: Support other types, emit to rest of plugins
if userActivity.activityType != NSUserActivityTypeBrowsingWeb || userActivity.webpageURL == nil {
return false
}

let url = userActivity.webpageURL
lastURL = url
NotificationCenter.default.post(name: .capacitorOpenUniversalLink, object: [
"url": url
])
return true
}
}
25 changes: 25 additions & 0 deletions @capacitor/ios/Capacitor/Capacitor/CAPBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

// the @available compiler directive does not provide an easy way to split apart string literals, so ignore the line length
// swiftlint:disable line_length
@available(*, deprecated, message: "'statusBarTappedNotification' has been moved to Notification.Name.capacitorStatusBarTapped. 'getLastUrl' and application delegate methods have been moved to ApplicationDelegateProxy.")
// swiftlint:enable line_length
@objc public class CAPBridge: NSObject {
@objc public static let statusBarTappedNotification = Notification(name: .capacitorStatusBarTapped)

public static func getLastUrl() -> URL? {
return ApplicationDelegateProxy.shared.lastURL
}

public static func handleOpenUrl(_ url: URL, _ options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
return ApplicationDelegateProxy.shared.application(UIApplication.shared, open: url, options: options)
}

public static func handleContinueActivity(_ userActivity: NSUserActivity, _ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return ApplicationDelegateProxy.shared.application(UIApplication.shared, continue: userActivity, restorationHandler: restorationHandler)
}

public static func handleAppBecameActive(_ application: UIApplication) {
// no-op for now
}
}
6 changes: 6 additions & 0 deletions @capacitor/ios/Capacitor/Capacitor/CAPBridgeDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public protocol CAPBridgeDelegate: AnyObject {
var bridgedWebView: WKWebView? { get }
var bridgedViewController: UIViewController? { get }
}
146 changes: 146 additions & 0 deletions @capacitor/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import Foundation
import WebKit

@objc public protocol CAPBridgeProtocol: NSObjectProtocol {
// MARK: - Environment Properties
var viewController: UIViewController? { get }
var config: InstanceConfiguration { get }
var webView: WKWebView? { get }
var notificationRouter: NotificationRouter { get }
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 }

// MARK: - Deprecated
@available(*, deprecated, renamed: "webView")
func getWebView() -> WKWebView?

@available(*, deprecated, renamed: "isSimEnvironment")
func isSimulator() -> Bool

@available(*, deprecated, renamed: "isDevEnvironment")
func isDevMode() -> Bool

@available(*, deprecated, renamed: "statusBarVisible")
func getStatusBarVisible() -> Bool

@available(*, deprecated, renamed: "statusBarStyle")
func getStatusBarStyle() -> UIStatusBarStyle

@available(*, deprecated, renamed: "userInterfaceStyle")
func getUserInterfaceStyle() -> UIUserInterfaceStyle

@available(*, deprecated, message: "Moved - equivalent is found on config.localURL")
func getLocalUrl() -> String

@available(*, deprecated, renamed: "savedCall(withID:)")
func getSavedCall(_ callbackId: String) -> CAPPluginCall?

@available(*, deprecated, renamed: "releaseCall(withID:)")
func releaseCall(callbackId: String)

// MARK: - Plugin Access
func plugin(withName: String) -> CAPPlugin?

// MARK: - Call Management
func saveCall(_ call: CAPPluginCall)
func savedCall(withID: String) -> CAPPluginCall?
func releaseCall(_ call: CAPPluginCall)
func releaseCall(withID: String)

// MARK: - JavaScript Handling
// `js` is a short name but needs to be preserved for backwards compatibility.
// swiftlint:disable identifier_name
func evalWithPlugin(_ plugin: CAPPlugin, js: String)
func eval(js: String)
// swiftlint:enable identifier_name

func triggerJSEvent(eventName: String, target: String)
func triggerJSEvent(eventName: String, target: String, data: String)

func triggerWindowJSEvent(eventName: String)
func triggerWindowJSEvent(eventName: String, data: String)

func triggerDocumentJSEvent(eventName: String)
func triggerDocumentJSEvent(eventName: String, data: String)

// MARK: - Paths, Files, Assets
func localURL(fromWebURL webURL: URL?) -> URL?
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)?)
func dismissVC(animated flag: Bool, completion: (() -> Void)?)
}

/*
Extensions to Obj-C protocols are not exposed to Obj-C code because of limitations in the runtime.
Therefore these methods are implicitly Swift-only.

The deprecated methods are declared here because they can be defined without colliding with the synthesized Obj-C setters
for the respective properties (e.g. `setStatusBarVisible:` for 'statusBarVisible`).
*/
extension CAPBridgeProtocol {
// variadic parameters cannot be exposed to Obj-C
@available(*, deprecated, message: "Use CAPLog directly")
public func modulePrint(_ plugin: CAPPlugin, _ items: Any...) {
let output = items.map { String(describing: $0) }.joined(separator: " ")
CAPLog.print("⚡️ ", plugin.pluginId, "-", output)
}

// default arguments are not permitted in protocol declarations
public func alert(_ title: String, _ message: String, _ buttonTitle: String = "OK") {
showAlertWith(title: title, message: message, buttonTitle: buttonTitle)
}

@available(*, deprecated, renamed: "statusBarVisible")
public func setStatusBarVisible(_ visible: Bool) {
statusBarVisible = visible
}

@available(*, deprecated, renamed: "statusBarStyle")
public func setStatusBarStyle(_ style: UIStatusBarStyle) {
statusBarStyle = style
}

@available(*, deprecated, renamed: "statusBarAnimation")
public func setStatusBarAnimation(_ animation: UIStatusBarAnimation) {
statusBarAnimation = animation
}
}

/*
Error(s) potentially exported by the bridge.
*/
public enum CapacitorBridgeError: Error {
case errorExportingCoreJS
}

extension CapacitorBridgeError: CustomNSError {
public static var errorDomain: String { "CapacitorBridge" }
public var errorCode: Int {
switch self {
case .errorExportingCoreJS:
return 0
}
}
public var errorUserInfo: [String: Any] {
return ["info": String(describing: self)]
}
}

extension CapacitorBridgeError: LocalizedError {
public var errorDescription: String? {
return NSLocalizedString("Unable to export JavaScript bridge code to webview", comment: "Capacitor bridge initialization error")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#import <Capacitor/Capacitor-Swift.h>

@interface CAPBridgeViewController (CDVScreenOrientationDelegate) <CDVScreenOrientationDelegate>

@end

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import "CAPBridgeViewController+CDVScreenOrientationDelegate.h"

@implementation CAPBridgeViewController (CDVScreenOrientationDelegate)

@end
Loading

0 comments on commit 21ed86f

Please sign in to comment.