@capgo/capacitor-inappbrowser 8.10.2 → 8.10.3-beta.pr621.476.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.
@@ -2,6 +2,7 @@ import Foundation
2
2
  import Capacitor
3
3
  import WebKit
4
4
  import AuthenticationServices
5
+ import SafariServices
5
6
 
6
7
  enum ActiveWebViewSupport {
7
8
  static func shouldActivateNewWebView(isHidden: Bool, hasActiveWebView: Bool) -> Bool {
@@ -115,6 +116,42 @@ enum ProxyResponseRoutingSupport {
115
116
  }
116
117
  }
117
118
 
119
+
120
+ enum StatusBarBackgroundLayoutSupport {
121
+ enum Placement: Equatable {
122
+ case skip
123
+ case host(pinToNavigationBar: Bool)
124
+ }
125
+
126
+ /// Decide where/how to pin the status-bar chrome without painting a PassThroughView gap.
127
+ static func placement(
128
+ isPassThroughOverlay: Bool,
129
+ blankNavigationTab: Bool,
130
+ navigationBarInHostHierarchy: Bool
131
+ ) -> Placement {
132
+ if isPassThroughOverlay && blankNavigationTab {
133
+ // Custom-frame blank overlays must stay transparent in the y-offset gap.
134
+ return .skip
135
+ }
136
+
137
+ if blankNavigationTab || !navigationBarInHostHierarchy {
138
+ return .host(pinToNavigationBar: false)
139
+ }
140
+
141
+ return .host(pinToNavigationBar: true)
142
+ }
143
+
144
+ static func hostView(
145
+ navigationControllerView: UIView,
146
+ framedContentView: UIView?
147
+ ) -> UIView? {
148
+ if let passThrough = navigationControllerView as? PassThroughView {
149
+ return framedContentView ?? passThrough.framedContentView
150
+ }
151
+ return navigationControllerView
152
+ }
153
+ }
154
+
118
155
  enum CustomWebViewFrameSupport {
119
156
  static func resolvedFrame(
120
157
  width: CGFloat?,
@@ -162,7 +199,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
162
199
  case aware = "AWARE"
163
200
  case fakeVisible = "FAKE_VISIBLE"
164
201
  }
165
- private let pluginVersion: String = "8.10.2"
202
+ private let pluginVersion: String = "8.10.3"
166
203
  public let identifier = "CapgoInAppBrowserPlugin"
167
204
  public let jsName = "CapgoInAppBrowser"
168
205
  public let pluginMethods: [CAPPluginMethod] = [
@@ -219,6 +256,8 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
219
256
  private var closeModalDescription: String?
220
257
  private var closeModalOk: String?
221
258
  private var closeModalCancel: String?
259
+ private var safariViewController: SFSafariViewController?
260
+ private var safariOpenedUrl: String?
222
261
  private var openSecureWindowCall: CAPPluginCall?
223
262
 
224
263
  private func setup() {
@@ -1583,14 +1622,10 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1583
1622
  // Apply status bar background color via the special view
1584
1623
  webViewController.setupStatusBarBackground(color: color)
1585
1624
 
1586
- // Apply background color to whole view to ensure no gaps
1625
+ // Color the webview controller only — never paint PassThroughView or the
1626
+ // y-offset gap would hide the host Capacitor app.
1587
1627
  webViewController.view.backgroundColor = color
1588
- self.navigationWebViewController?.view.backgroundColor = color
1589
-
1590
- // Apply status bar background color
1591
- if let navController = self.navigationWebViewController {
1592
- navController.view.backgroundColor = color
1593
- }
1628
+ self.applyBlankToolbarBackground(color, to: self.navigationWebViewController)
1594
1629
  } else {
1595
1630
  // Follow system appearance if no specific color
1596
1631
  let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
@@ -1601,10 +1636,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1601
1636
  // Apply status bar background color via the special view
1602
1637
  webViewController.setupStatusBarBackground(color: backgroundColor)
1603
1638
 
1604
- // Set appropriate background color
1605
- if let navController = self.navigationWebViewController {
1606
- navController.view.backgroundColor = backgroundColor
1607
- }
1639
+ self.applyBlankToolbarBackground(backgroundColor, to: self.navigationWebViewController)
1608
1640
  }
1609
1641
 
1610
1642
  }
@@ -2052,6 +2084,20 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
2052
2084
  }
2053
2085
  }
2054
2086
 
2087
+
2088
+ /// Keep PassThroughView clear so custom y-offsets show the host app behind the gap.
2089
+ private func applyBlankToolbarBackground(_ color: UIColor, to navigationController: UINavigationController?) {
2090
+ guard let navigationController else { return }
2091
+
2092
+ if let passThrough = navigationController.view as? PassThroughView {
2093
+ passThrough.backgroundColor = .clear
2094
+ passThrough.framedContentView?.backgroundColor = color
2095
+ return
2096
+ }
2097
+
2098
+ navigationController.view.backgroundColor = color
2099
+ }
2100
+
2055
2101
  private func normalizedAuthorizedHosts(from links: [String]) -> [String] {
2056
2102
  links.compactMap { link in
2057
2103
  guard let host = URLComponents(string: link)?.host?.lowercased() else {
@@ -2140,12 +2186,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
2140
2186
  self.setup()
2141
2187
  }
2142
2188
 
2143
- let isInspectable = call.getBool("isInspectable", false)
2144
- let preventDeeplink = call.getBool("preventDeeplink", false)
2145
- self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
2146
-
2147
- self.currentPluginCall = call
2148
-
2149
2189
  guard let urlString = call.getString("url") else {
2150
2190
  call.reject("Must provide a URL to open")
2151
2191
  return
@@ -2156,70 +2196,42 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
2156
2196
  return
2157
2197
  }
2158
2198
 
2159
- let headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
2160
- let credentials = self.readCredentials(call)
2199
+ guard let url = URL(string: urlString),
2200
+ let scheme = url.scheme?.lowercased(),
2201
+ ["http", "https"].contains(scheme) else {
2202
+ call.reject("Must provide a valid http(s) URL to open")
2203
+ return
2204
+ }
2161
2205
 
2162
2206
  DispatchQueue.main.async {
2163
- guard let webSource = self.webSource(for: urlString) else {
2164
- call.reject("Invalid URL format")
2207
+ if self.safariViewController != nil {
2208
+ call.reject("Browser is already open")
2165
2209
  return
2166
2210
  }
2167
2211
 
2168
- let initialUrl = webSource.remoteURL ?? URL(string: "about:blank")!
2212
+ let safariVC = SFSafariViewController(url: url)
2213
+ safariVC.delegate = self
2214
+ safariVC.modalPresentationStyle = .fullScreen
2169
2215
 
2170
- self.webViewController = WKWebViewController.init(url: initialUrl, headers: headers, isInspectable: isInspectable, credentials: credentials, preventDeeplink: preventDeeplink, blankNavigationTab: true, enabledSafeBottomMargin: false, enabledSafeTopMargin: true)
2216
+ self.safariViewController = safariVC
2217
+ self.safariOpenedUrl = url.absoluteString
2171
2218
 
2172
- guard let webViewController = self.webViewController else {
2173
- call.reject("Failed to initialize WebViewController")
2219
+ guard let rootPresenter = self.bridge?.viewController else {
2220
+ self.safariViewController = nil
2221
+ self.safariOpenedUrl = nil
2222
+ call.reject("Unable to present Safari View Controller")
2174
2223
  return
2175
2224
  }
2176
-
2177
- if self.bridge?.statusBarVisible == true {
2178
- let subviews = self.bridge?.webView?.superview?.subviews
2179
- if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
2180
- if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
2181
- webViewController.capacitorStatusBar = emptyStatusBar
2182
- emptyStatusBar.removeFromSuperview()
2183
- }
2184
- }
2225
+ let top = BlankTargetNavigationSupport.topPresenter(from: rootPresenter)
2226
+ guard top.presentedViewController == nil else {
2227
+ self.safariViewController = nil
2228
+ self.safariOpenedUrl = nil
2229
+ call.reject("Unable to present Safari View Controller: another view is already presented")
2230
+ return
2185
2231
  }
2186
-
2187
- webViewController.source = webSource
2188
- webViewController.leftNavigationBarItemTypes = [.back, .forward, .reload]
2189
- webViewController.capBrowserPlugin = self
2190
- webViewController.hasDynamicTitle = true
2191
-
2192
- self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
2193
- self.navigationWebViewController?.navigationBar.isTranslucent = false
2194
-
2195
- // Ensure no lines or borders appear by default
2196
- self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
2197
- self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
2198
- self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
2199
-
2200
- // Use system appearance
2201
- let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
2202
- let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
2203
- let textColor = isDarkMode ? UIColor.white : UIColor.black
2204
-
2205
- // Apply colors
2206
- webViewController.setupStatusBarBackground(color: backgroundColor)
2207
- webViewController.tintColor = textColor
2208
- self.navigationWebViewController?.navigationBar.tintColor = textColor
2209
- self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
2210
- webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
2211
- webViewController.updateStatusBarStyle()
2212
-
2213
- // Always hide toolbar to ensure no bottom bar
2214
- self.navigationWebViewController?.setToolbarHidden(true, animated: false)
2215
-
2216
- self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
2217
- self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
2218
-
2219
- if !self.isPresentAfterPageLoad {
2220
- self.presentView()
2232
+ top.present(safariVC, animated: true) {
2233
+ call.resolve()
2221
2234
  }
2222
- call.resolve()
2223
2235
  }
2224
2236
  }
2225
2237
 
@@ -2316,6 +2328,17 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
2316
2328
  let isAnimated = call.getBool("isAnimated", true)
2317
2329
 
2318
2330
  DispatchQueue.main.async {
2331
+ if call.getString("id") == nil, let safariViewController = self.safariViewController {
2332
+ let url = self.safariOpenedUrl ?? ""
2333
+ self.safariViewController = nil
2334
+ self.safariOpenedUrl = nil
2335
+ safariViewController.dismiss(animated: isAnimated) {
2336
+ self.notifyListeners("closeEvent", data: ["url": url])
2337
+ call.resolve()
2338
+ }
2339
+ return
2340
+ }
2341
+
2319
2342
  let targetId = call.getString("id") ?? self.activeWebViewId
2320
2343
  if let targetId,
2321
2344
  let webViewController = self.webViewControllers[targetId],
@@ -2359,6 +2382,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
2359
2382
  }
2360
2383
  }
2361
2384
 
2385
+
2362
2386
  private func showPrivacyScreen() {
2363
2387
  if privacyScreen == nil {
2364
2388
  let newPrivacyScreen = UIImageView()
@@ -2540,3 +2564,23 @@ extension CapgoInAppBrowserPlugin: ASWebAuthenticationPresentationContextProvidi
2540
2564
  return self.bridge?.viewController?.view.window ?? ASPresentationAnchor()
2541
2565
  }
2542
2566
  }
2567
+
2568
+ extension CapgoInAppBrowserPlugin: SFSafariViewControllerDelegate {
2569
+ public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
2570
+ guard safariViewController != nil else {
2571
+ return
2572
+ }
2573
+ let url = safariOpenedUrl ?? ""
2574
+ safariViewController = nil
2575
+ safariOpenedUrl = nil
2576
+ notifyListeners("closeEvent", data: ["url": url])
2577
+ }
2578
+
2579
+ public func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
2580
+ if didLoadSuccessfully {
2581
+ notifyListeners("browserPageLoaded", data: [:])
2582
+ } else {
2583
+ notifyListeners("pageLoadError", data: [:])
2584
+ }
2585
+ }
2586
+ }
@@ -47,6 +47,35 @@ private enum BlobDownloadSupport {
47
47
  static let chunkBytes = 64 * 1024
48
48
  }
49
49
 
50
+ /// Script message handlers registered on every in-app browser WKWebView.
51
+ /// Popup configs from `createWebViewWith` copy parent handlers, so we must
52
+ /// remove these names before re-adding or WKWebView raises NSInvalidArgumentException.
53
+ enum ScriptMessageHandlerSupport {
54
+ static let allNames = [
55
+ "messageHandler",
56
+ "preShowScriptError",
57
+ "preShowScriptSuccess",
58
+ "close",
59
+ "hide",
60
+ "show",
61
+ "blobDownload",
62
+ "blobDownloadStart",
63
+ "blobDownloadChunk",
64
+ "blobDownloadFinish",
65
+ "blobDownloadAbort",
66
+ "takeScreenshot",
67
+ "consoleMessageHandler",
68
+ "magicPrint",
69
+ "capgoProxyBridge"
70
+ ]
71
+
72
+ static func removeAll(from userContentController: WKUserContentController) {
73
+ for name in allNames {
74
+ userContentController.removeScriptMessageHandler(forName: name)
75
+ }
76
+ }
77
+ }
78
+
50
79
  private struct BlobDownloadSession {
51
80
  let destinationURL: URL
52
81
  let sourceURL: String?
@@ -1101,7 +1130,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1101
1130
  open var statusBarStyle: UIStatusBarStyle = .default
1102
1131
 
1103
1132
  // Status bar background view
1104
- private var statusBarBackgroundView: UIView?
1133
+ var statusBarBackgroundView: UIView?
1105
1134
 
1106
1135
  // Status bar height
1107
1136
  private var statusBarHeight: CGFloat {
@@ -1151,25 +1180,60 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1151
1180
  // Make status bar background with colored view underneath
1152
1181
  open func setupStatusBarBackground(color: UIColor) {
1153
1182
  statusBarBackgroundView?.removeFromSuperview()
1154
- statusBarBackgroundView = UIView()
1183
+ statusBarBackgroundView = nil
1155
1184
 
1156
- guard let navController = navigationController,
1157
- let navView = navController.view,
1158
- let statusBarBackgroundView else {
1185
+ guard let navController = navigationController else {
1159
1186
  return
1160
1187
  }
1161
- navView.insertSubview(statusBarBackgroundView, at: 0)
1162
- statusBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
1163
- statusBarBackgroundView.backgroundColor = color
1164
1188
 
1165
- NSLayoutConstraint.activate([
1166
- statusBarBackgroundView.topAnchor.constraint(equalTo: navView.topAnchor),
1167
- statusBarBackgroundView.leadingAnchor.constraint(equalTo: navView.leadingAnchor),
1168
- statusBarBackgroundView.trailingAnchor.constraint(equalTo: navView.trailingAnchor),
1169
- statusBarBackgroundView.bottomAnchor.constraint(equalTo: navController.navigationBar.bottomAnchor, constant: 1)
1170
- ])
1189
+ if let passThrough = navController.view as? PassThroughView {
1190
+ // Never paint the full-screen overlay — that fills the custom y-offset gap.
1191
+ passThrough.backgroundColor = .clear
1192
+ }
1193
+
1194
+ let navigationBar = navController.navigationBar
1195
+ let hostCandidate = StatusBarBackgroundLayoutSupport.hostView(
1196
+ navigationControllerView: navController.view,
1197
+ framedContentView: (navController.view as? PassThroughView)?.framedContentView
1198
+ )
1199
+ let navigationBarInHost = hostCandidate.map { navigationBar.isDescendant(of: $0) } ?? false
1200
+ let placement = StatusBarBackgroundLayoutSupport.placement(
1201
+ isPassThroughOverlay: navController.view is PassThroughView,
1202
+ blankNavigationTab: blankNavigationTab,
1203
+ navigationBarInHostHierarchy: navigationBarInHost
1204
+ )
1205
+
1206
+ guard case .host(let pinToNavigationBar) = placement,
1207
+ let hostView = hostCandidate else {
1208
+ return
1209
+ }
1171
1210
 
1172
- applyNavigationBarBackground(color: color)
1211
+ let backgroundView = UIView()
1212
+ statusBarBackgroundView = backgroundView
1213
+ hostView.insertSubview(backgroundView, at: 0)
1214
+ backgroundView.translatesAutoresizingMaskIntoConstraints = false
1215
+ backgroundView.backgroundColor = color
1216
+
1217
+ var constraints = [
1218
+ backgroundView.topAnchor.constraint(equalTo: hostView.topAnchor),
1219
+ backgroundView.leadingAnchor.constraint(equalTo: hostView.leadingAnchor),
1220
+ backgroundView.trailingAnchor.constraint(equalTo: hostView.trailingAnchor)
1221
+ ]
1222
+
1223
+ if pinToNavigationBar {
1224
+ constraints.append(
1225
+ backgroundView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 1)
1226
+ )
1227
+ applyNavigationBarBackground(color: color)
1228
+ } else {
1229
+ // Blank toolbar / detached nav bar: cover status-bar height only.
1230
+ // Avoid pinning to a hidden UINavigationBar (removed from hierarchy → crash).
1231
+ constraints.append(
1232
+ backgroundView.heightAnchor.constraint(equalToConstant: max(statusBarHeight, 20))
1233
+ )
1234
+ }
1235
+
1236
+ NSLayoutConstraint.activate(constraints)
1173
1237
  }
1174
1238
 
1175
1239
  // Override to use our custom status bar style
@@ -1973,16 +2037,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1973
2037
  }
1974
2038
  let userContentController = webConfiguration.userContentController
1975
2039
  userContentController.removeAllUserScripts()
1976
- userContentController.removeScriptMessageHandler(forName: "messageHandler")
1977
- userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
1978
- userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
1979
- userContentController.removeScriptMessageHandler(forName: "close")
1980
- userContentController.removeScriptMessageHandler(forName: "hide")
1981
- userContentController.removeScriptMessageHandler(forName: "show")
1982
- userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
1983
- userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
1984
- userContentController.removeScriptMessageHandler(forName: "magicPrint")
1985
- userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
2040
+ ScriptMessageHandlerSupport.removeAll(from: userContentController)
1986
2041
 
1987
2042
  if proxyRequests || proxySchemeHandler != nil, let handler = proxySchemeHandler {
1988
2043
  WKWebView.enableCustomSchemeHandling(for: ["https", "http"])
@@ -2229,7 +2284,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
2229
2284
  self.enabledSafeTopMargin = parent.enabledSafeTopMargin
2230
2285
  self.blockedHosts = parent.blockedHosts
2231
2286
  self.authorizedAppLinks = parent.authorizedAppLinks
2232
- self.activeNativeNavigationForWebview = parent.activeNativeNavigationForWebview
2287
+ // createWebViewWith configs copy parent script handlers; start clean so
2288
+ // initWebview can register blobDownload/etc without NSInvalidArgumentException.
2289
+ configuration.userContentController = WKUserContentController()
2233
2290
  self.initialWebConfiguration = configuration
2234
2291
  self.persistWebViewData = parent.persistWebViewData
2235
2292
  self.disableOverscroll = parent.disableOverscroll
@@ -2513,20 +2570,7 @@ public extension WKWebViewController {
2513
2570
  webView.loadHTMLString("", baseURL: nil)
2514
2571
 
2515
2572
  webView.configuration.userContentController.removeAllUserScripts()
2516
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "messageHandler")
2517
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "close")
2518
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "hide")
2519
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "show")
2520
- if allowScreenshotsFromWebPage {
2521
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
2522
- }
2523
- if captureConsoleLogs {
2524
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
2525
- }
2526
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
2527
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
2528
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "magicPrint")
2529
- webView.configuration.userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
2573
+ ScriptMessageHandlerSupport.removeAll(from: webView.configuration.userContentController)
2530
2574
 
2531
2575
  webView.removeFromSuperview()
2532
2576
  // Also clean progress bar view if present
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.10.2",
3
+ "version": "8.10.3-beta.pr621.476.1",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",