@capgo/capacitor-webview-crash 8.1.10 → 8.1.12

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/README.md CHANGED
@@ -1,24 +1,48 @@
1
1
  # @capgo/capacitor-webview-crash
2
2
 
3
- <a href="https://capgo.app/">
3
+ Keep your Capacitor app alive when the WebView crashes: detect recovered crashes, restart dead WebViews natively, and recycle long-running WebViews before they run out of memory.
4
+
5
+ <a href="https://capgo.app/?ref=plugin_webview_crash">
4
6
  <img
5
- src="https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png"
6
- alt="Capgo - Instant updates for Capacitor"
7
+ src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-webview-crash"
8
+ alt="Capgo - Build Capacitor apps with AI, ship updates instantly"
7
9
  />
8
10
  </a>
9
11
 
10
12
  <div align="center">
13
+ <p>
14
+ <b>Capgo</b>: push fixes to your Capacitor users in minutes, build signed iOS and Android apps without a Mac, and
15
+ roll back in one click.
16
+ </p>
11
17
  <h2>
12
- <a href="https://capgo.app/?ref=plugin_webview_crash"> ➡️ Get Instant updates for your App with Capgo</a>
18
+ <a href="https://capgo.app/register/?ref=plugin_webview_crash">➡️ Get started for free</a>
13
19
  </h2>
14
- <h2>
20
+ <p>14-day unlimited free trial. No credit card required</p>
21
+ <p>
15
22
  <a href="https://capgo.app/consulting/?ref=plugin_webview_crash">
16
- Missing a feature? We’ll build the plugin for you 💪
23
+ Missing a feature? We'll build the plugin for you 💪
17
24
  </a>
18
- </h2>
25
+ </p>
19
26
  </div>
20
27
 
21
- Detect recovered Capacitor WebView crashes, restart dead WebViews natively, and optionally recycle long-running WebViews on a fixed interval before memory pressure turns into an OOM.
28
+ > 🤖 **Building with an AI coding agent?** Install [Capgo skills](https://capgo.app/skills/?ref=plugin_webview_crash) so Claude Code, Codex, Cursor, Copilot, or Gemini CLI know how to install and use `@capgo/capacitor-webview-crash` in your Capacitor app: `npx skills add Cap-go/capgo-skills`. Then ask your agent: _"Add @capgo/capacitor-webview-crash to my Capacitor app and wire it up."_ [Agent setup guides](https://capgo.app/agents/?ref=plugin_webview_crash) · [Hosted MCP](https://capgo.app/docs/ai/mcp/?ref=plugin_webview_crash)
29
+
30
+ <p align="center">
31
+ <img
32
+ src="https://raw.githubusercontent.com/Cap-go/capacitor-webview-crash/main/assets/github-social-preview.png"
33
+ alt="@capgo/capacitor-webview-crash for Capacitor apps"
34
+ width="300"
35
+ />
36
+ </p>
37
+
38
+ ## Key features
39
+
40
+ - **Crash recovery**: `restartOnCrash` restarts the WebView when its renderer process dies.
41
+ - **Recovery info**: `getPendingCrashInfo()` and `clearPendingCrashInfo()`, plus the `webViewRestoredAfterCrash` event.
42
+ - **Scheduled recycling**: `restartIntervalMs` or `restartCron` replaces long-running WebViews.
43
+ - **Manual restart**: `restartWebView()` creates a fresh WebView.
44
+ - **Testing**: `simulateCrashRecovery()` exercises your recovery flow.
45
+ - **Platforms**: iOS and Android. Web only supports the testing helpers.
22
46
 
23
47
  ## What It Does
24
48
 
@@ -1,6 +1,5 @@
1
1
  import Capacitor
2
2
  import Foundation
3
- import ObjectiveC.runtime
4
3
  import UIKit
5
4
  import WebKit
6
5
 
@@ -292,31 +291,98 @@ enum WebViewCrashStore {
292
291
  }
293
292
  }
294
293
 
295
- enum WebViewCrashSwizzler {
296
- private static var didInstall = false
297
-
298
- static func installIfNeeded() {
299
- guard !didInstall else {
294
+ enum WebViewCrashNavigationDelegateInstaller {
295
+ /// Retains the proxy because `WKWebView.navigationDelegate` is weak.
296
+ private static var retainedProxy: WebViewCrashNavigationDelegateProxy?
297
+
298
+ /// Installs a typed `WKNavigationDelegate` proxy on the bridge web view.
299
+ ///
300
+ /// Ordering: call after Capacitor assigns `navigationDelegate` (plugin `load()` or after
301
+ /// `loadWebView()`). If another plugin replaces `navigationDelegate` later, crash handling
302
+ /// stops until this installer runs again. Manual or periodic restarts re-install via
303
+ /// `recreateBridgeWebView()`.
304
+ static func installIfNeeded(on webView: WKWebView?) {
305
+ guard let webView else {
300
306
  return
301
307
  }
302
308
 
303
- let originalSelector = #selector(WebViewDelegationHandler.webViewWebContentProcessDidTerminate(_:))
304
- let swizzledSelector = #selector(WebViewDelegationHandler.capgo_webViewCrash_webViewWebContentProcessDidTerminate(_:))
309
+ if webView.navigationDelegate is WebViewCrashNavigationDelegateProxy {
310
+ return
311
+ }
305
312
 
306
- guard
307
- let originalMethod = class_getInstanceMethod(WebViewDelegationHandler.self, originalSelector),
308
- let swizzledMethod = class_getInstanceMethod(WebViewDelegationHandler.self, swizzledSelector)
309
- else {
313
+ guard let handler = webView.navigationDelegate as? WebViewDelegationHandler else {
310
314
  return
311
315
  }
312
316
 
313
- method_exchangeImplementations(originalMethod, swizzledMethod)
314
- didInstall = true
317
+ let proxy = WebViewCrashNavigationDelegateProxy(handler: handler)
318
+ retainedProxy = proxy
319
+ webView.navigationDelegate = proxy
315
320
  }
316
321
  }
317
322
 
318
- private extension WebViewDelegationHandler {
319
- @objc func capgo_webViewCrash_webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
323
+ final class WebViewCrashNavigationDelegateProxy: NSObject, WKNavigationDelegate {
324
+ private weak var handler: WebViewDelegationHandler?
325
+
326
+ init(handler: WebViewDelegationHandler) {
327
+ self.handler = handler
328
+ super.init()
329
+ }
330
+
331
+ func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
332
+ handler?.webView(webView, didStartProvisionalNavigation: navigation)
333
+ }
334
+
335
+ func webView(
336
+ _ webView: WKWebView,
337
+ requestMediaCapturePermissionFor origin: WKSecurityOrigin,
338
+ initiatedByFrame frame: WKFrameInfo,
339
+ type: WKMediaCaptureType,
340
+ decisionHandler: @escaping (WKPermissionDecision) -> Void
341
+ ) {
342
+ handler?.webView(
343
+ webView,
344
+ requestMediaCapturePermissionFor: origin,
345
+ initiatedByFrame: frame,
346
+ type: type,
347
+ decisionHandler: decisionHandler
348
+ )
349
+ }
350
+
351
+ func webView(
352
+ _ webView: WKWebView,
353
+ requestDeviceOrientationAndMotionPermissionFor origin: WKSecurityOrigin,
354
+ initiatedByFrame frame: WKFrameInfo,
355
+ decisionHandler: @escaping (WKPermissionDecision) -> Void
356
+ ) {
357
+ handler?.webView(
358
+ webView,
359
+ requestDeviceOrientationAndMotionPermissionFor: origin,
360
+ initiatedByFrame: frame,
361
+ decisionHandler: decisionHandler
362
+ )
363
+ }
364
+
365
+ func webView(
366
+ _ webView: WKWebView,
367
+ decidePolicyFor navigationAction: WKNavigationAction,
368
+ decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
369
+ ) {
370
+ handler?.webView(webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler)
371
+ }
372
+
373
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
374
+ handler?.webView(webView, didFinish: navigation)
375
+ }
376
+
377
+ func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
378
+ handler?.webView(webView, didFail: navigation, withError: error)
379
+ }
380
+
381
+ func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
382
+ handler?.webView(webView, didFailProvisionalNavigation: navigation, withError: error)
383
+ }
384
+
385
+ func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
320
386
  let crashInfo = WebViewCrashStore.buildCrashInfo(
321
387
  platform: "ios",
322
388
  reason: "webContentProcessDidTerminate",
@@ -330,17 +396,28 @@ private extension WebViewDelegationHandler {
330
396
  return
331
397
  }
332
398
 
333
- let restart = {
334
- self.capgo_webViewCrash_webViewWebContentProcessDidTerminate(webView)
399
+ let forwardToCapacitor: () -> Void = { [weak self] in
400
+ guard let handler = self?.handler else {
401
+ return
402
+ }
403
+ handler.webViewWebContentProcessDidTerminate(webView)
335
404
  }
336
405
 
337
406
  let delay = WebViewCrashRuntime.restartAfterCrashDelaySeconds
338
407
  if delay > 0 {
339
- DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: restart)
408
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: forwardToCapacitor)
340
409
  } else {
341
- restart()
410
+ forwardToCapacitor()
342
411
  }
343
412
  }
413
+
414
+ func webView(
415
+ _ webView: WKWebView,
416
+ didReceive challenge: URLAuthenticationChallenge,
417
+ completionHandler: @escaping @MainActor (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
418
+ ) {
419
+ handler?.webView(webView, didReceive: challenge, completionHandler: completionHandler)
420
+ }
344
421
  }
345
422
 
346
423
  extension UIApplication.State {
@@ -20,7 +20,7 @@ public class WebViewCrashPlugin: CAPPlugin, CAPBridgedPlugin {
20
20
  override public func load() {
21
21
  restartOptions = WebViewCrashRestartOptions(config: getConfig())
22
22
  WebViewCrashRuntime.update(options: restartOptions)
23
- WebViewCrashSwizzler.installIfNeeded()
23
+ WebViewCrashNavigationDelegateInstaller.installIfNeeded(on: bridge?.webView)
24
24
  schedulePeriodicRestart()
25
25
  }
26
26
 
@@ -147,6 +147,7 @@ public class WebViewCrashPlugin: CAPPlugin, CAPBridgedPlugin {
147
147
  viewController.webView?.uiDelegate = nil
148
148
  viewController.loadView()
149
149
  viewController.loadWebView()
150
+ WebViewCrashNavigationDelegateInstaller.installIfNeeded(on: viewController.webView)
150
151
  return true
151
152
  }
152
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-webview-crash",
3
- "version": "8.1.10",
3
+ "version": "8.1.12",
4
4
  "description": "Capacitor plugin for detecting WebView crash recovery and restarting long-running WebViews natively.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",