@capgo/capacitor-webview-crash 8.0.2 → 8.1.0

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.
@@ -9,20 +9,31 @@ public class WebViewCrashPlugin: CAPPlugin, CAPBridgedPlugin {
9
9
  public let pluginMethods: [CAPPluginMethod] = [
10
10
  CAPPluginMethod(name: "getPendingCrashInfo", returnType: CAPPluginReturnPromise),
11
11
  CAPPluginMethod(name: "clearPendingCrashInfo", returnType: CAPPluginReturnPromise),
12
- CAPPluginMethod(name: "simulateCrashRecovery", returnType: CAPPluginReturnPromise)
12
+ CAPPluginMethod(name: "simulateCrashRecovery", returnType: CAPPluginReturnPromise),
13
+ CAPPluginMethod(name: "restartWebView", returnType: CAPPluginReturnPromise)
13
14
  ]
14
15
 
15
- private var didDispatchPendingEvent = false
16
+ private var dispatchedPendingEvents = Set<String>()
17
+ private var restartOptions = WebViewCrashRestartOptions()
18
+ private var restartTimer: Timer?
16
19
 
17
20
  override public func load() {
21
+ restartOptions = WebViewCrashRestartOptions(config: getConfig())
22
+ WebViewCrashRuntime.update(options: restartOptions)
18
23
  WebViewCrashSwizzler.installIfNeeded()
24
+ schedulePeriodicRestart()
25
+ }
26
+
27
+ deinit {
28
+ restartTimer?.invalidate()
19
29
  }
20
30
 
21
31
  @objc override public func addListener(_ call: CAPPluginCall) {
22
32
  super.addListener(call)
23
33
 
24
- if call.getString("eventName") == WebViewCrashBridge.eventName {
25
- dispatchPendingCrashIfNeeded()
34
+ if let eventName = call.getString("eventName"),
35
+ eventName == WebViewCrashBridge.crashEventName || eventName == WebViewCrashBridge.restartEventName {
36
+ dispatchPendingCrashIfNeeded(eventName: eventName)
26
37
  }
27
38
  }
28
39
 
@@ -32,7 +43,7 @@ public class WebViewCrashPlugin: CAPPlugin, CAPBridgedPlugin {
32
43
 
33
44
  @objc func clearPendingCrashInfo(_ call: CAPPluginCall) {
34
45
  WebViewCrashStore.clear()
35
- didDispatchPendingEvent = false
46
+ dispatchedPendingEvents.removeAll()
36
47
  call.resolve()
37
48
  }
38
49
 
@@ -45,18 +56,97 @@ public class WebViewCrashPlugin: CAPPlugin, CAPBridgedPlugin {
45
56
  )
46
57
 
47
58
  WebViewCrashStore.write(crashInfo)
48
- didDispatchPendingEvent = false
49
- dispatchPendingCrashIfNeeded()
59
+ dispatchedPendingEvents.removeAll()
60
+ dispatchPendingCrashIfNeeded(eventName: WebViewCrashBridge.crashEventName)
61
+ dispatchPendingCrashIfNeeded(eventName: WebViewCrashBridge.restartEventName)
50
62
 
51
63
  call.resolve(WebViewCrashBridge.pendingResult(crashInfo))
52
64
  }
53
65
 
54
- private func dispatchPendingCrashIfNeeded() {
55
- guard !didDispatchPendingEvent, let crashInfo = WebViewCrashStore.read() else {
66
+ @objc func restartWebView(_ call: CAPPluginCall) {
67
+ guard bridge?.viewController is CAPBridgeViewController else {
68
+ call.reject("Unable to restart WebView because the bridge view controller is unavailable.")
69
+ return
70
+ }
71
+
72
+ let restartInfo = WebViewCrashStore.buildCrashInfo(
73
+ platform: "ios",
74
+ reason: WebViewCrashBridge.manualRestartReason,
75
+ url: bridge?.webView?.url?.absoluteString,
76
+ appState: UIApplication.shared.applicationState.capgoWebViewCrashValue
77
+ )
78
+
79
+ WebViewCrashStore.write(restartInfo)
80
+ dispatchedPendingEvents.removeAll()
81
+ call.resolve(WebViewCrashBridge.pendingResult(restartInfo))
82
+
83
+ DispatchQueue.main.async { [weak self] in
84
+ _ = self?.recreateBridgeWebView()
85
+ }
86
+ }
87
+
88
+ private func dispatchPendingCrashIfNeeded(eventName: String) {
89
+ guard !dispatchedPendingEvents.contains(eventName),
90
+ let crashInfo = WebViewCrashStore.read(),
91
+ WebViewCrashStore.shouldDispatch(eventName: eventName, crashInfo: crashInfo) else {
56
92
  return
57
93
  }
58
94
 
59
- didDispatchPendingEvent = true
60
- notifyListeners(WebViewCrashBridge.eventName, data: crashInfo)
95
+ dispatchedPendingEvents.insert(eventName)
96
+ notifyListeners(eventName, data: crashInfo)
97
+ }
98
+
99
+ private func schedulePeriodicRestart() {
100
+ restartTimer?.invalidate()
101
+
102
+ guard let restartDelaySeconds = restartOptions.nextRestartDelaySeconds, restartDelaySeconds > 0 else {
103
+ return
104
+ }
105
+
106
+ DispatchQueue.main.async { [weak self] in
107
+ guard let self else {
108
+ return
109
+ }
110
+
111
+ restartTimer = Timer.scheduledTimer(withTimeInterval: restartDelaySeconds, repeats: false) { [weak self] _ in
112
+ self?.restartWebView(reason: WebViewCrashBridge.periodicRestartReason)
113
+ }
114
+ }
115
+ }
116
+
117
+ private func restartWebView(reason: String) {
118
+ DispatchQueue.main.async { [weak self] in
119
+ guard let self, let webView = bridge?.webView else {
120
+ return
121
+ }
122
+
123
+ let restartInfo = WebViewCrashStore.buildCrashInfo(
124
+ platform: "ios",
125
+ reason: reason,
126
+ url: webView.url?.absoluteString,
127
+ appState: UIApplication.shared.applicationState.capgoWebViewCrashValue
128
+ )
129
+
130
+ WebViewCrashStore.write(restartInfo)
131
+ dispatchedPendingEvents.removeAll()
132
+ if !recreateBridgeWebView() {
133
+ webView.reload()
134
+ schedulePeriodicRestart()
135
+ }
136
+ }
137
+ }
138
+
139
+ private func recreateBridgeWebView() -> Bool {
140
+ guard let viewController = bridge?.viewController as? CAPBridgeViewController else {
141
+ return false
142
+ }
143
+
144
+ restartTimer?.invalidate()
145
+ viewController.webView?.stopLoading()
146
+ viewController.webView?.navigationDelegate = nil
147
+ viewController.webView?.uiDelegate = nil
148
+ viewController.loadView()
149
+ viewController.loadWebView()
150
+ return true
61
151
  }
62
152
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-webview-crash",
3
- "version": "8.0.2",
4
- "description": "Capacitor plugin for detecting WebView crash recovery and informing the next JS runtime.",
3
+ "version": "8.1.0",
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",
7
7
  "types": "dist/esm/index.d.ts",
@@ -29,6 +29,8 @@
29
29
  "capacitor",
30
30
  "plugin",
31
31
  "webview-crash",
32
+ "webview-restart",
33
+ "oom-recovery",
32
34
  "crash-recovery",
33
35
  "ios",
34
36
  "android",
@@ -36,7 +38,7 @@
36
38
  ],
37
39
  "scripts": {
38
40
  "verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
39
- "verify:ios": "xcodebuild -scheme CapgoCapacitorWebViewCrash -destination generic/platform=iOS",
41
+ "verify:ios": "xcodebuild -scheme CapgoCapacitorWebviewCrash -destination generic/platform=iOS",
40
42
  "verify:android": "cd android && ./gradlew clean build test && cd ..",
41
43
  "verify:web": "bun run build && bun test",
42
44
  "test": "bun test",