@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.
@@ -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
- if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene {
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
- if let scene = notification.object as? UIWindowScene, scene === self?.viewController?.view.window?.windowScene {
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
- if let clazz = NSClassFromString("SSLPinningHttpRequestHandlerClass") {
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(self.request(_:)), with: [
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
- SEL originalSelector = NSSelectorFromString(@"handleTapAction:");
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
- let frameworkName = "WK"
35
- let className = "ContentView"
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: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
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
- handleCapacitorHttpRequest(urlSchemeTask, localUrl, false)
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
- "Access-Control-Allow-Origin": self.serverUrl?.absoluteString ?? "",
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 = NSSelectorFromString("shouldOverrideLoad:")
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 = NSSelectorFromString("handleWKWebViewURLAuthenticationChallenge:completionHandler:")
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.5.2",
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.5.0",
29
- "@capacitor/core": "^8.5.0"
28
+ "@capacitor-plus/core": "^8.6.0",
29
+ "@capacitor/core": "^8.6.0"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"