@capgo/capacitor-inappbrowser 8.10.1 → 8.10.2

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.
@@ -2649,6 +2649,33 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2649
2649
  WebView.HitTestResult result = view.getHitTestResult();
2650
2650
  String data = result != null ? result.getExtra() : null;
2651
2651
 
2652
+ // Authorized App Links with target=_blank should open the native app,
2653
+ // not spawn a managed popup (which previously left the UI in a broken state).
2654
+ if (
2655
+ data != null &&
2656
+ !_options.getPreventDeeplink() &&
2657
+ isHttpOrHttpsUrl(data) &&
2658
+ isAuthorizedAppLink(data, _options.getAuthorizedAppLinks())
2659
+ ) {
2660
+ try {
2661
+ Log.d("InAppBrowser", "Opening authorized blank-target link externally: " + data);
2662
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data));
2663
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2664
+ view.getContext().startActivity(intent);
2665
+ return false;
2666
+ } catch (ActivityNotFoundException | SecurityException e) {
2667
+ Log.w("InAppBrowser", "No app for authorized blank-target link, loading in current WebView", e);
2668
+ final String fallbackUrl = data;
2669
+ view.post(() -> {
2670
+ if (isDismissing || _webView == null || _webView != view) {
2671
+ return;
2672
+ }
2673
+ _webView.loadUrl(fallbackUrl);
2674
+ });
2675
+ return false;
2676
+ }
2677
+ }
2678
+
2652
2679
  if (shouldLoadBlankTargetInCurrentWebView(data)) {
2653
2680
  Log.d("InAppBrowser", "Loading target=_blank link in current WebView: " + data);
2654
2681
  view.post(() -> _webView.loadUrl(data));
@@ -13,6 +13,70 @@ enum ActiveWebViewSupport {
13
13
  }
14
14
  }
15
15
 
16
+ enum BlankTargetNavigationSupport {
17
+ enum Action: Equatable {
18
+ case openExternalApp
19
+ case loadInCurrentWebView
20
+ case createPopup
21
+ }
22
+
23
+ /// Decides how a `target=_blank` / new-window request should be handled.
24
+ static func resolve(
25
+ urlIsHttpOrHttps: Bool,
26
+ openBlankTargetInWebView: Bool,
27
+ preventDeeplink: Bool,
28
+ isAuthorizedAppLink: Bool
29
+ ) -> Action {
30
+ guard urlIsHttpOrHttps else {
31
+ return .createPopup
32
+ }
33
+
34
+ if isAuthorizedAppLink && !preventDeeplink {
35
+ return .openExternalApp
36
+ }
37
+
38
+ if openBlankTargetInWebView || preventDeeplink {
39
+ return .loadInCurrentWebView
40
+ }
41
+
42
+ return .createPopup
43
+ }
44
+
45
+ /// Prefer the parent content VC so we never present from a UINavigationController
46
+ /// whose root view was replaced (custom width/height PassThroughView).
47
+ static func popupPresenter(
48
+ parentController: UIViewController,
49
+ bridgeViewController: UIViewController?
50
+ ) -> UIViewController {
51
+ if parentController.view.window != nil {
52
+ return parentController
53
+ }
54
+
55
+ if let navigationController = parentController.navigationController,
56
+ navigationController.view.window != nil {
57
+ if let visible = navigationController.visibleViewController,
58
+ visible.view.window != nil {
59
+ return visible
60
+ }
61
+ return navigationController
62
+ }
63
+
64
+ var top = bridgeViewController
65
+ while let presented = top?.presentedViewController {
66
+ top = presented
67
+ }
68
+ return top ?? parentController
69
+ }
70
+
71
+ static func topPresenter(from root: UIViewController) -> UIViewController {
72
+ var top = root
73
+ while let presented = top.presentedViewController {
74
+ top = presented
75
+ }
76
+ return top
77
+ }
78
+ }
79
+
16
80
  protocol ProxyRequestLocating {
17
81
  func hasPendingProxyRequest(_ requestId: String) -> Bool
18
82
  }
@@ -98,7 +162,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
98
162
  case aware = "AWARE"
99
163
  case fakeVisible = "FAKE_VISIBLE"
100
164
  }
101
- private let pluginVersion: String = "8.10.1"
165
+ private let pluginVersion: String = "8.10.2"
102
166
  public let identifier = "CapgoInAppBrowserPlugin"
103
167
  public let jsName = "CapgoInAppBrowser"
104
168
  public let pluginMethods: [CAPPluginMethod] = [
@@ -314,8 +378,25 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
314
378
  return nil
315
379
  }
316
380
  } else {
317
- let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
318
- presenter?.present(navigationController, animated: true, completion: nil)
381
+ navigationController.modalPresentationStyle = .overFullScreen
382
+ navigationController.modalTransitionStyle = .crossDissolve
383
+
384
+ let presenter = BlankTargetNavigationSupport.popupPresenter(
385
+ parentController: parentController,
386
+ bridgeViewController: self.bridge?.viewController
387
+ )
388
+ presentNavigationControllerSafely(navigationController, from: presenter, animated: true) { [weak self] presented in
389
+ if !presented {
390
+ self?.unregisterWebView(id: popupId)
391
+ }
392
+ self?.notifyPopupWindowOpened(
393
+ id: popupId,
394
+ parentId: parentController.instanceId,
395
+ url: navigationAction.request.url?.absoluteString,
396
+ visible: presented
397
+ )
398
+ }
399
+ return popupWebView
319
400
  }
320
401
  notifyPopupWindowOpened(
321
402
  id: popupId,
@@ -326,6 +407,43 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
326
407
  return popupWebView
327
408
  }
328
409
 
410
+ private func presentNavigationControllerSafely(
411
+ _ navigationController: UINavigationController,
412
+ from presenter: UIViewController?,
413
+ animated: Bool,
414
+ completion: ((Bool) -> Void)? = nil
415
+ ) {
416
+ guard let presenter else {
417
+ completion?(false)
418
+ return
419
+ }
420
+
421
+ let presentBlock = {
422
+ if navigationController.presentingViewController != nil {
423
+ completion?(true)
424
+ return
425
+ }
426
+
427
+ let top = BlankTargetNavigationSupport.topPresenter(from: presenter)
428
+ guard top.presentedViewController == nil else {
429
+ print("[InAppBrowser] Skipping present; presenter already has a presented VC")
430
+ completion?(false)
431
+ return
432
+ }
433
+ top.present(navigationController, animated: animated) {
434
+ completion?(true)
435
+ }
436
+ }
437
+
438
+ if Thread.isMainThread,
439
+ !presenter.isBeingPresented,
440
+ !presenter.isBeingDismissed {
441
+ presentBlock()
442
+ } else {
443
+ DispatchQueue.main.async(execute: presentBlock)
444
+ }
445
+ }
446
+
329
447
  private func resolveWebViewController(for id: String?) -> WKWebViewController? {
330
448
  if let id {
331
449
  return webViewControllers[id]
@@ -421,9 +539,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
421
539
  }
422
540
  dismissActiveKeyboard()
423
541
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
424
- presenter?.present(navigationController, animated: isAnimated, completion: {
425
- self.currentPluginCall?.resolve()
426
- })
542
+ presentNavigationControllerSafely(navigationController, from: presenter, animated: isAnimated) { presented in
543
+ if presented {
544
+ self.currentPluginCall?.resolve()
545
+ } else {
546
+ self.currentPluginCall?.reject("Failed to present webview")
547
+ }
548
+ }
427
549
  return true
428
550
  }
429
551
 
@@ -1610,9 +1732,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1610
1732
 
1611
1733
  if navController.presentingViewController == nil {
1612
1734
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
1613
- presenter?.present(navController, animated: true, completion: {
1614
- call?.resolve()
1615
- })
1735
+ self.presentNavigationControllerSafely(navController, from: presenter, animated: true) { presented in
1736
+ if presented {
1737
+ call?.resolve()
1738
+ } else {
1739
+ call?.reject("Failed to present webview")
1740
+ }
1741
+ }
1616
1742
  return
1617
1743
  }
1618
1744
  }
@@ -1664,7 +1790,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1664
1790
  }
1665
1791
  }
1666
1792
  }
1667
-
1668
1793
  @objc func bringToFront(_ call: CAPPluginCall) {
1669
1794
  DispatchQueue.main.async {
1670
1795
  let isAnimated = call.getBool("isAnimated", true)
@@ -1685,9 +1810,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1685
1810
  self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navigationController)
1686
1811
  if navigationController.presentingViewController == nil {
1687
1812
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
1688
- presenter?.present(navigationController, animated: isAnimated, completion: {
1689
- call.resolve()
1690
- })
1813
+ self.presentNavigationControllerSafely(navigationController, from: presenter, animated: isAnimated) { presented in
1814
+ if presented {
1815
+ call.resolve()
1816
+ } else {
1817
+ call.reject("Failed to present webview")
1818
+ }
1819
+ }
1691
1820
  return
1692
1821
  }
1693
1822
  call.resolve()
@@ -3211,6 +3211,31 @@ extension WKWebViewController: WKUIDelegate {
3211
3211
  }
3212
3212
 
3213
3213
  print("[InAppBrowser] Handling popup/new window request for URL: \(url.absoluteString)")
3214
+
3215
+ let isAuthorized = isUrlAuthorized(url, authorizedLinks: authorizedAppLinks)
3216
+ switch BlankTargetNavigationSupport.resolve(
3217
+ urlIsHttpOrHttps: isHttpOrHttps(url),
3218
+ openBlankTargetInWebView: openBlankTargetInWebView,
3219
+ preventDeeplink: preventDeeplink,
3220
+ isAuthorizedAppLink: isAuthorized
3221
+ ) {
3222
+ case .openExternalApp:
3223
+ // Prefer the native app / Universal Link. If none handles it, keep browsing in-place.
3224
+ tryOpenUniversalLink(url) { [weak webView] opened in
3225
+ if !opened {
3226
+ DispatchQueue.main.async {
3227
+ webView?.load(navigationAction.request)
3228
+ }
3229
+ }
3230
+ }
3231
+ return nil
3232
+ case .loadInCurrentWebView:
3233
+ webView.load(navigationAction.request)
3234
+ return nil
3235
+ case .createPopup:
3236
+ break
3237
+ }
3238
+
3214
3239
  return capBrowserPlugin?.createManagedPopupWebView(
3215
3240
  from: self,
3216
3241
  configuration: configuration,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.10.1",
3
+ "version": "8.10.2",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",