@capacitor-plus/ios 8.5.2 → 8.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Capacitor/Capacitor/CAPAppStorePrivateAPI.swift +10 -0
- package/Capacitor/Capacitor/CapacitorBridge.swift +24 -7
- package/Capacitor/Capacitor/Plugins/CapacitorHttp.swift +3 -2
- package/Capacitor/Capacitor/UIStatusBarManager+CAPHandleTapAction.m +9 -1
- package/Capacitor/Capacitor/WKWebView+Capacitor.swift +4 -4
- package/Capacitor/Capacitor/WebViewAssetHandler.swift +17 -5
- package/Capacitor/Capacitor/WebViewDelegationHandler.swift +43 -2
- package/Capacitor/Capacitor/assets/native-bridge.js +0 -6
- package/package.json +4 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Compile-time names for private WebKit / plugin integration points (App Store 2.5.2 static dispatch).
|
|
4
|
+
enum CAPAppStorePrivateAPI {
|
|
5
|
+
static let wkContentViewClassName = "WKContentView"
|
|
6
|
+
static let wkElementDidFocusSelector = Selector("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
|
|
7
|
+
static let sslPinningHttpRequestHandlerClassName = "SSLPinningHttpRequestHandlerClass"
|
|
8
|
+
static let shouldOverrideLoadSelector = #selector(CAPPlugin.shouldOverrideLoad(_:))
|
|
9
|
+
static let handleWKWebViewURLAuthenticationChallengeSelector = #selector(CAPPlugin.handleWKWebViewURLAuthenticationChallenge(_:completionHandler:))
|
|
10
|
+
}
|
|
@@ -213,6 +213,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
|
|
|
213
213
|
super.init()
|
|
214
214
|
|
|
215
215
|
self.webViewDelegationHandler.bridge = self
|
|
216
|
+
self.webViewAssetHandler.setConfiguration(configuration)
|
|
216
217
|
|
|
217
218
|
exportCoreJS(localUrl: configuration.localURL.absoluteString)
|
|
218
219
|
registerPlugins()
|
|
@@ -272,19 +273,35 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
|
|
|
272
273
|
self?.triggerDocumentJSEvent(eventName: "pause")
|
|
273
274
|
})
|
|
274
275
|
observers.append(NotificationCenter.default.addObserver(forName: UIScene.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in
|
|
275
|
-
|
|
276
|
-
self?.triggerDocumentJSEvent(eventName: "resume")
|
|
277
|
-
}
|
|
278
|
-
|
|
276
|
+
self?.triggerSceneLifecycleJSEvent("resume", for: notification)
|
|
279
277
|
})
|
|
280
278
|
observers.append(NotificationCenter.default.addObserver(forName: UIScene.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in
|
|
281
|
-
|
|
282
|
-
self?.triggerDocumentJSEvent(eventName: "pause")
|
|
283
|
-
}
|
|
279
|
+
self?.triggerSceneLifecycleJSEvent("pause", for: notification)
|
|
284
280
|
})
|
|
285
281
|
}
|
|
286
282
|
}
|
|
287
283
|
|
|
284
|
+
/**
|
|
285
|
+
Forward a scene lifecycle transition to the page as a document event, but only once
|
|
286
|
+
the page exists to receive it.
|
|
287
|
+
|
|
288
|
+
On a cold start `UIScene.willEnterForegroundNotification` is posted while the initial
|
|
289
|
+
load is still in flight, before `window.Capacitor` has been defined. Evaluating
|
|
290
|
+
`triggerEvent` at that point throws inside the web view and surfaces as a
|
|
291
|
+
"JS Eval error" in the log. The same happens when the web content process was
|
|
292
|
+
terminated and the page is being reloaded. In both cases the page is about to load
|
|
293
|
+
from scratch, so there is nothing for it to resume or pause; the event is dropped
|
|
294
|
+
rather than deferred, because a "resume" delivered right after a fresh load would
|
|
295
|
+
make the page react to a transition it never went through.
|
|
296
|
+
*/
|
|
297
|
+
private func triggerSceneLifecycleJSEvent(_ eventName: String, for notification: Notification) {
|
|
298
|
+
guard let scene = notification.object as? UIWindowScene,
|
|
299
|
+
scene === viewController?.view.window?.windowScene else {
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
eval(js: "window.Capacitor?.triggerEvent && window.Capacitor.triggerEvent('\(eventName)', 'document')")
|
|
303
|
+
}
|
|
304
|
+
|
|
288
305
|
/**
|
|
289
306
|
Export the core Cordova JS runtime
|
|
290
307
|
*/
|
|
@@ -15,9 +15,10 @@ public class CAPHttpPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
15
15
|
|
|
16
16
|
@objc func http(_ call: CAPPluginCall, _ httpMethod: String?) {
|
|
17
17
|
do {
|
|
18
|
-
|
|
18
|
+
// appstore-2.5.2-allow: optional SSL pinning handler plugin class lookup
|
|
19
|
+
if let clazz = NSClassFromString(CAPAppStorePrivateAPI.sslPinningHttpRequestHandlerClassName) {
|
|
19
20
|
// swiftlint:disable force_cast
|
|
20
|
-
(clazz as! NSObject.Type).perform(#selector(
|
|
21
|
+
(clazz as! NSObject.Type).perform(#selector(CAPHttpPlugin.request(_:)), with: [
|
|
21
22
|
"call": call,
|
|
22
23
|
"httpMethod": httpMethod as Any,
|
|
23
24
|
"config": self.bridge?.config as Any
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
#import <objc/message.h>
|
|
3
3
|
#import <objc/runtime.h>
|
|
4
4
|
|
|
5
|
+
// appstore-2.5.2-allow: forward declare private UIStatusBarManager API for compile-time @selector
|
|
6
|
+
@interface UIStatusBarManager (CAPPrivateStatusBarTap)
|
|
7
|
+
- (void)handleTapAction:(id)arg1;
|
|
8
|
+
@end
|
|
9
|
+
|
|
5
10
|
@implementation UIStatusBarManager (CAPHandleTapAction)
|
|
6
11
|
|
|
7
12
|
+ (void)load {
|
|
8
13
|
static dispatch_once_t onceToken;
|
|
9
14
|
dispatch_once(&onceToken, ^{
|
|
10
15
|
Class class = [self class];
|
|
11
|
-
|
|
16
|
+
// appstore-2.5.2-allow: status bar tap notification for CapacitorStatusBarTapped
|
|
17
|
+
SEL originalSelector = @selector(handleTapAction:);
|
|
12
18
|
SEL swizzledSelector = @selector(nofity_handleTapAction:);
|
|
13
19
|
|
|
14
20
|
Method originalMethod = class_getInstanceMethod(class, originalSelector);
|
|
@@ -19,11 +25,13 @@
|
|
|
19
25
|
method_getImplementation(swizzledMethod),
|
|
20
26
|
method_getTypeEncoding(swizzledMethod));
|
|
21
27
|
if (didAddMethod) {
|
|
28
|
+
// appstore-2.5.2-allow: install status bar tap swizzle when method was not present
|
|
22
29
|
class_replaceMethod(class,
|
|
23
30
|
swizzledSelector,
|
|
24
31
|
method_getImplementation(originalMethod),
|
|
25
32
|
method_getTypeEncoding(originalMethod));
|
|
26
33
|
} else {
|
|
34
|
+
// appstore-2.5.2-allow: exchange status bar tap implementation
|
|
27
35
|
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
28
36
|
}
|
|
29
37
|
});
|
|
@@ -31,9 +31,8 @@ internal extension WKWebView {
|
|
|
31
31
|
// dispatch_once isn't available in Swift, but lazy properties use the same mechanism under the hood so
|
|
32
32
|
// we can safely assume that this block of code will only execute once.
|
|
33
33
|
static let oneTimeOnlySwizzle: () = {
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
guard let targetClass = NSClassFromString(frameworkName + className) else {
|
|
34
|
+
// appstore-2.5.2-allow: WKContentView keyboard focus integration (compile-time class name)
|
|
35
|
+
guard let targetClass = NSClassFromString(CAPAppStorePrivateAPI.wkContentViewClassName) else {
|
|
37
36
|
return
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -59,10 +58,11 @@ internal extension WKWebView {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
let imp: IMP = imp_implementationWithBlock(block)
|
|
61
|
+
// appstore-2.5.2-allow: replace WKContentView focus handler implementation
|
|
62
62
|
method_setImplementation(method, imp)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
let selectorMkIV
|
|
65
|
+
let selectorMkIV = CAPAppStorePrivateAPI.wkElementDidFocusSelector
|
|
66
66
|
|
|
67
67
|
if let method = class_getInstanceMethod(targetClass, selectorMkIV) {
|
|
68
68
|
swizzleFiveArgClosure(method, selectorMkIV)
|
|
@@ -6,6 +6,7 @@ import UniformTypeIdentifiers
|
|
|
6
6
|
open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
7
7
|
private var router: Router
|
|
8
8
|
private var serverUrl: URL?
|
|
9
|
+
private var configuration: InstanceConfiguration?
|
|
9
10
|
|
|
10
11
|
public init(router: Router) {
|
|
11
12
|
self.router = router
|
|
@@ -20,6 +21,10 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
20
21
|
self.serverUrl = serverUrl
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
open func setConfiguration(_ configuration: InstanceConfiguration?) {
|
|
25
|
+
self.configuration = configuration
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
private func isUsingLiveReload(_ localUrl: URL) -> Bool {
|
|
24
29
|
return self.serverUrl != nil && self.serverUrl?.scheme != localUrl.scheme
|
|
25
30
|
}
|
|
@@ -31,7 +36,13 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
31
36
|
let localUrl = URL.init(string: url.absoluteString)!
|
|
32
37
|
|
|
33
38
|
if url.path.starts(with: CapacitorBridge.httpInterceptorStartIdentifier) {
|
|
34
|
-
|
|
39
|
+
// Only serve the proxy when CapacitorHttp is on. A scheme task can't tell a document from
|
|
40
|
+
// a subresource, so keeping documents out relies on the check in decidePolicyFor.
|
|
41
|
+
if configuration?.getPluginConfig("CapacitorHttp").getBoolean("enabled", false) == true {
|
|
42
|
+
handleCapacitorHttpRequest(urlSchemeTask, localUrl, false)
|
|
43
|
+
} else {
|
|
44
|
+
urlSchemeTask.didFailWithError(URLError(.unsupportedURL))
|
|
45
|
+
}
|
|
35
46
|
return
|
|
36
47
|
}
|
|
37
48
|
|
|
@@ -152,12 +163,13 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
152
163
|
let existingHeaders = response.allHeaderFields
|
|
153
164
|
var newHeaders: [AnyHashable: Any] = [:]
|
|
154
165
|
|
|
166
|
+
// Nothing should render this. If anything does, sandbox keeps it inert.
|
|
167
|
+
newHeaders["Content-Security-Policy"] = "sandbox; frame-ancestors 'none'"
|
|
168
|
+
|
|
155
169
|
// if using live reload, then set CORS headers
|
|
156
170
|
if self.isUsingLiveReload(url) {
|
|
157
|
-
newHeaders =
|
|
158
|
-
|
|
159
|
-
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, TRACE"
|
|
160
|
-
]
|
|
171
|
+
newHeaders["Access-Control-Allow-Origin"] = self.serverUrl?.absoluteString ?? ""
|
|
172
|
+
newHeaders["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS, TRACE"
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
if let mergedHeaders = existingHeaders.merging(newHeaders, uniquingKeysWith: { (_, newHeaders) in newHeaders }) as? [String: String] {
|
|
@@ -64,6 +64,35 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat
|
|
|
64
64
|
decisionHandler(.grant)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
private static func effectivePort(for url: URL) -> Int? {
|
|
68
|
+
if let port = url.port {
|
|
69
|
+
return port
|
|
70
|
+
}
|
|
71
|
+
switch url.scheme?.lowercased() {
|
|
72
|
+
case "https":
|
|
73
|
+
return 443
|
|
74
|
+
case "http":
|
|
75
|
+
return 80
|
|
76
|
+
default:
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static func matchesOrigin(_ url: URL, _ origin: URL) -> Bool {
|
|
82
|
+
guard url.scheme == origin.scheme, url.host == origin.host else {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
return effectivePort(for: url) == effectivePort(for: origin)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Test seam: avoids subclassing `WKNavigationAction` in unit tests.
|
|
89
|
+
static func shouldBlockHttpInterceptorNavigation(for url: URL, serverURL: URL, localURL: URL) -> Bool {
|
|
90
|
+
guard url.path.starts(with: CapacitorBridge.httpInterceptorStartIdentifier) else {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
return matchesOrigin(url, serverURL) || matchesOrigin(url, localURL)
|
|
94
|
+
}
|
|
95
|
+
|
|
67
96
|
open func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
68
97
|
// post a notification for any listeners
|
|
69
98
|
NotificationCenter.default.post(name: .capacitorDecidePolicyForNavigationAction, object: navigationAction)
|
|
@@ -74,10 +103,21 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat
|
|
|
74
103
|
return
|
|
75
104
|
}
|
|
76
105
|
|
|
106
|
+
// The proxy returns a remote body at the app origin, so block it before plugins can allow it.
|
|
107
|
+
if Self.shouldBlockHttpInterceptorNavigation(
|
|
108
|
+
for: navURL,
|
|
109
|
+
serverURL: bridge.config.serverURL,
|
|
110
|
+
localURL: bridge.config.localURL
|
|
111
|
+
) {
|
|
112
|
+
decisionHandler(.cancel)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
77
116
|
// first, give plugins the chance to handle the decision
|
|
78
117
|
for pluginObject in bridge.plugins {
|
|
79
118
|
let plugin = pluginObject.value
|
|
80
|
-
let selector =
|
|
119
|
+
let selector = CAPAppStorePrivateAPI.shouldOverrideLoadSelector
|
|
120
|
+
// appstore-2.5.2-allow: optional plugin navigation override hook
|
|
81
121
|
if plugin.responds(to: selector) {
|
|
82
122
|
let shouldOverrideLoad = plugin.shouldOverrideLoad(navigationAction)
|
|
83
123
|
if shouldOverrideLoad != nil {
|
|
@@ -169,7 +209,8 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat
|
|
|
169
209
|
|
|
170
210
|
for pluginObject in bridge.plugins {
|
|
171
211
|
let plugin = pluginObject.value
|
|
172
|
-
let selector =
|
|
212
|
+
let selector = CAPAppStorePrivateAPI.handleWKWebViewURLAuthenticationChallengeSelector
|
|
213
|
+
// appstore-2.5.2-allow: optional plugin TLS authentication hook
|
|
173
214
|
if plugin.responds(to: selector) {
|
|
174
215
|
if plugin.handleWKWebViewURLAuthenticationChallenge(challenge, completionHandler: completionHandler) {
|
|
175
216
|
return
|
|
@@ -417,12 +417,6 @@ var nativeBridge = (function (exports) {
|
|
|
417
417
|
}
|
|
418
418
|
};
|
|
419
419
|
const platform = getPlatformId(win);
|
|
420
|
-
if (platform == 'android' && typeof win.CapacitorSystemBarsAndroidInterface !== 'undefined') {
|
|
421
|
-
// add DOM ready listener for System Bars
|
|
422
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
423
|
-
win.CapacitorSystemBarsAndroidInterface.onDOMReady();
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
420
|
if (platform == 'android' || platform == 'ios') {
|
|
427
421
|
// patch document.cookie on Android/iOS
|
|
428
422
|
win.CapacitorCookiesDescriptor =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor-plus/ios",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.1",
|
|
4
4
|
"description": "Capacitor+: Enhanced Capacitor with automated upstream sync - Cross-platform apps with JavaScript and the web",
|
|
5
5
|
"homepage": "https://capgo.app/docs/plugins/capacitor-plus/",
|
|
6
6
|
"author": "Capgo Team <support@capgo.app> (https://capgo.app)",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"scripts/pods_helpers.rb"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"verify": "npm run xc:build:Capacitor && npm run xc:build:CapacitorCordova",
|
|
23
|
+
"verify": "node ../scripts/check-ios-dynamic-dispatch.mjs && npm run xc:build:Capacitor && npm run xc:build:CapacitorCordova",
|
|
24
24
|
"xc:build:Capacitor": "cd Capacitor && xcodebuild clean test -workspace Capacitor.xcworkspace -scheme Capacitor -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' && cd ..",
|
|
25
25
|
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@capacitor-plus/core": "^8.
|
|
29
|
-
"@capacitor/core": "^8.
|
|
28
|
+
"@capacitor-plus/core": "^8.6.0",
|
|
29
|
+
"@capacitor/core": "^8.6.0"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|