@capgo/capacitor-updater 8.45.11 → 8.46.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.
- package/Package.swift +1 -1
- package/README.md +40 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +558 -12
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +45 -0
- package/dist/docs.json +50 -1
- package/dist/esm/definitions.d.ts +46 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +4 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +4 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AppHealthTracker.swift +82 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +66 -8
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +9 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +1 -0
- package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +276 -0
- package/package.json +10 -1
|
@@ -47,8 +47,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
47
47
|
CAPPluginMethod(name: "setMultiDelay", returnType: CAPPluginReturnPromise),
|
|
48
48
|
CAPPluginMethod(name: "cancelDelay", returnType: CAPPluginReturnPromise),
|
|
49
49
|
CAPPluginMethod(name: "getLatest", returnType: CAPPluginReturnPromise),
|
|
50
|
+
CAPPluginMethod(name: "triggerUpdateCheck", returnType: CAPPluginReturnPromise),
|
|
50
51
|
CAPPluginMethod(name: "setChannel", returnType: CAPPluginReturnPromise),
|
|
51
52
|
CAPPluginMethod(name: "unsetChannel", returnType: CAPPluginReturnPromise),
|
|
53
|
+
CAPPluginMethod(name: "reportWebViewError", returnType: CAPPluginReturnPromise),
|
|
52
54
|
CAPPluginMethod(name: "getChannel", returnType: CAPPluginReturnPromise),
|
|
53
55
|
CAPPluginMethod(name: "listChannels", returnType: CAPPluginReturnPromise),
|
|
54
56
|
CAPPluginMethod(name: "setCustomId", returnType: CAPPluginReturnPromise),
|
|
@@ -74,7 +76,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
74
76
|
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
75
77
|
]
|
|
76
78
|
public var implementation = CapgoUpdater()
|
|
77
|
-
private let pluginVersion: String = "8.
|
|
79
|
+
private let pluginVersion: String = "8.46.0"
|
|
78
80
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
79
81
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
80
82
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -130,6 +132,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
130
132
|
private var persistModifyUrl = false
|
|
131
133
|
private var allowManualBundleError = false
|
|
132
134
|
private var keepUrlPathFlagLastValue: Bool?
|
|
135
|
+
private var appHealthTracker: AppHealthTracker?
|
|
136
|
+
private var webViewStatsReporter: WebViewStatsReporter?
|
|
133
137
|
public var shakeMenuEnabled = false
|
|
134
138
|
public var shakeChannelSelectorEnabled = false
|
|
135
139
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
@@ -145,6 +149,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
145
149
|
} else {
|
|
146
150
|
logger.error("Failed to get webView for logging")
|
|
147
151
|
}
|
|
152
|
+
let webViewStatsReporter = WebViewStatsReporter(implementation: implementation)
|
|
153
|
+
self.webViewStatsReporter = webViewStatsReporter
|
|
154
|
+
webViewStatsReporter.install(on: self.bridge?.webView)
|
|
148
155
|
#if targetEnvironment(simulator)
|
|
149
156
|
logger.info("::::: SIMULATOR :::::")
|
|
150
157
|
logger.info("Application directory: \(NSHomeDirectory())")
|
|
@@ -225,12 +232,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
225
232
|
resetWhenUpdate = getConfig().getBoolean("resetWhenUpdate", true)
|
|
226
233
|
shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
227
234
|
shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
228
|
-
|
|
229
|
-
if periodCheckDelayValue >= 0 && periodCheckDelayValue > 600 {
|
|
230
|
-
periodCheckDelay = 600
|
|
231
|
-
} else {
|
|
232
|
-
periodCheckDelay = periodCheckDelayValue
|
|
233
|
-
}
|
|
235
|
+
periodCheckDelay = Self.normalizedPeriodCheckDelaySeconds(getConfig().getInt("periodCheckDelay", 0))
|
|
234
236
|
|
|
235
237
|
implementation.setPublicKey(getConfig().getString("publicKey") ?? "")
|
|
236
238
|
implementation.notifyDownloadRaw = notifyDownload
|
|
@@ -289,6 +291,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
289
291
|
implementation.defaultChannel = getConfig().getString("defaultChannel", "")!
|
|
290
292
|
}
|
|
291
293
|
self.implementation.autoReset()
|
|
294
|
+
let appHealthTracker = AppHealthTracker(implementation: self.implementation)
|
|
295
|
+
self.appHealthTracker = appHealthTracker
|
|
296
|
+
appHealthTracker.reportPreviousUncleanForegroundExit()
|
|
297
|
+
appHealthTracker.startSession()
|
|
292
298
|
|
|
293
299
|
// Check if app was recently installed/updated BEFORE cleanup updates the stored native build version.
|
|
294
300
|
self.wasRecentlyInstalledOrUpdated = self.checkIfRecentlyInstalledOrUpdated()
|
|
@@ -312,6 +318,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
312
318
|
let nc = NotificationCenter.default
|
|
313
319
|
nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
|
|
314
320
|
nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
321
|
+
nc.addObserver(self, selector: #selector(appWillTerminate), name: UIApplication.willTerminateNotification, object: nil)
|
|
322
|
+
nc.addObserver(self, selector: #selector(appDidReceiveMemoryWarning), name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
|
|
315
323
|
|
|
316
324
|
// Check for 'kill' delay condition on app launch
|
|
317
325
|
// This handles cases where the app was killed (willTerminateNotification is not reliable for system kills)
|
|
@@ -368,6 +376,22 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
378
|
|
|
379
|
+
@objc private func appWillTerminate() {
|
|
380
|
+
appHealthTracker?.markForeground(false)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@objc private func appDidReceiveMemoryWarning() {
|
|
384
|
+
appHealthTracker?.reportMemoryWarning()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@objc func reportWebViewError(_ call: CAPPluginCall) {
|
|
388
|
+
guard let webViewStatsReporter = webViewStatsReporter else {
|
|
389
|
+
call.resolve()
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
webViewStatsReporter.reportError(call)
|
|
393
|
+
}
|
|
394
|
+
|
|
371
395
|
private func initialLoad() -> Bool {
|
|
372
396
|
guard let bridge = self.bridge else { return false }
|
|
373
397
|
if keepUrlPathAfterReload {
|
|
@@ -1028,6 +1052,27 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1028
1052
|
}
|
|
1029
1053
|
}
|
|
1030
1054
|
|
|
1055
|
+
public func triggerBackgroundUpdateCheck() -> String {
|
|
1056
|
+
guard !self.updateUrl.isEmpty, URL(string: self.updateUrl) != nil else {
|
|
1057
|
+
logger.error("Error no url or wrong format")
|
|
1058
|
+
return "unavailable"
|
|
1059
|
+
}
|
|
1060
|
+
if self.isDownloadStuckOrTimedOut() {
|
|
1061
|
+
logger.info("Download already in progress, skipping duplicate download request")
|
|
1062
|
+
return "already_running"
|
|
1063
|
+
}
|
|
1064
|
+
self.backgroundDownload()
|
|
1065
|
+
return "queued"
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
@objc func triggerUpdateCheck(_ call: CAPPluginCall) {
|
|
1069
|
+
let status = self.triggerBackgroundUpdateCheck()
|
|
1070
|
+
call.resolve([
|
|
1071
|
+
"status": status,
|
|
1072
|
+
"queued": status == "queued"
|
|
1073
|
+
])
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1031
1076
|
@objc func unsetChannel(_ call: CAPPluginCall) {
|
|
1032
1077
|
let triggerAutoUpdate = call.getBool("triggerAutoUpdate", false)
|
|
1033
1078
|
self.saveCallForAsyncHandling(call)
|
|
@@ -1719,6 +1764,13 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1719
1764
|
plannedDirectUpdate && directUpdateMode == "onLaunch"
|
|
1720
1765
|
}
|
|
1721
1766
|
|
|
1767
|
+
static func normalizedPeriodCheckDelaySeconds(_ value: Int) -> Int {
|
|
1768
|
+
guard value > 0 else {
|
|
1769
|
+
return 0
|
|
1770
|
+
}
|
|
1771
|
+
return max(600, value)
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1722
1774
|
private func getOnLaunchDirectUpdateUsed() -> Bool {
|
|
1723
1775
|
self.onLaunchDirectUpdateStateLock.lock()
|
|
1724
1776
|
defer { self.onLaunchDirectUpdateStateLock.unlock() }
|
|
@@ -1769,13 +1821,17 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1769
1821
|
self.notifyListeners("majorAvailable", data: payload)
|
|
1770
1822
|
}
|
|
1771
1823
|
|
|
1772
|
-
|
|
1824
|
+
static func normalizedUpdateResponseKind(kind: String?) -> String {
|
|
1773
1825
|
if let kind, ["up_to_date", "blocked", "failed"].contains(kind) {
|
|
1774
1826
|
return kind
|
|
1775
1827
|
}
|
|
1776
1828
|
return "failed"
|
|
1777
1829
|
}
|
|
1778
1830
|
|
|
1831
|
+
private func updateResponseKind(kind: String?) -> String {
|
|
1832
|
+
Self.normalizedUpdateResponseKind(kind: kind)
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1779
1835
|
private func endBackgroundDownloadAfterLatestError(
|
|
1780
1836
|
backendError: String,
|
|
1781
1837
|
res: AppVersion,
|
|
@@ -2153,6 +2209,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2153
2209
|
}
|
|
2154
2210
|
|
|
2155
2211
|
@objc func appMovedToForeground() {
|
|
2212
|
+
appHealthTracker?.markForeground(true)
|
|
2156
2213
|
let current: BundleInfo = self.implementation.getCurrentBundle()
|
|
2157
2214
|
self.implementation.sendStats(action: "app_moved_to_foreground", versionName: current.getVersionName())
|
|
2158
2215
|
self.delayUpdateUtils.checkCancelDelay(source: .foreground)
|
|
@@ -2219,6 +2276,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2219
2276
|
@objc func appMovedToBackground() {
|
|
2220
2277
|
// Reset timeout flag at start of each background cycle
|
|
2221
2278
|
self.autoSplashscreenTimedOut = false
|
|
2279
|
+
appHealthTracker?.markForeground(false)
|
|
2222
2280
|
|
|
2223
2281
|
let current: BundleInfo = self.implementation.getCurrentBundle()
|
|
2224
2282
|
self.implementation.sendStats(action: "app_moved_to_background", versionName: current.getVersionName())
|
|
@@ -2256,6 +2256,14 @@ import UIKit
|
|
|
2256
2256
|
}()
|
|
2257
2257
|
|
|
2258
2258
|
func sendStats(action: String, versionName: String? = nil, oldVersionName: String? = "") {
|
|
2259
|
+
sendStatsWithMetadata(action: action, versionName: versionName, oldVersionName: oldVersionName, metadata: nil)
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
func sendStats(action: String, versionName: String?, oldVersionName: String?, metadata: [String: String]) {
|
|
2263
|
+
sendStatsWithMetadata(action: action, versionName: versionName, oldVersionName: oldVersionName, metadata: metadata)
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
private func sendStatsWithMetadata(action: String, versionName: String?, oldVersionName: String?, metadata: [String: String]?) {
|
|
2259
2267
|
// Check if rate limit was exceeded
|
|
2260
2268
|
if CapgoUpdater.rateLimitExceeded {
|
|
2261
2269
|
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.")
|
|
@@ -2286,6 +2294,7 @@ import UIKit
|
|
|
2286
2294
|
channel: info.channel,
|
|
2287
2295
|
defaultChannel: info.defaultChannel,
|
|
2288
2296
|
key_id: info.key_id,
|
|
2297
|
+
metadata: metadata,
|
|
2289
2298
|
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
|
|
2290
2299
|
)
|
|
2291
2300
|
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
final class WebViewStatsReporter {
|
|
6
|
+
static let script = """
|
|
7
|
+
(function(){
|
|
8
|
+
if(window.__capgoWebViewErrorReporterInstalled){return;}
|
|
9
|
+
window.__capgoWebViewErrorReporterInstalled=true;
|
|
10
|
+
var maxReports=20,sentReports=0,queue=[],seen={};
|
|
11
|
+
var sessionKey='CapacitorUpdater.webViewSession';
|
|
12
|
+
var sessionId=String(Date.now())+'-'+Math.random().toString(36).slice(2);
|
|
13
|
+
function s(value){
|
|
14
|
+
try{
|
|
15
|
+
if(value===undefined){return '';}
|
|
16
|
+
if(value===null){return 'null';}
|
|
17
|
+
if(typeof value==='string'){return value;}
|
|
18
|
+
if(value&&typeof value.message==='string'){return value.message;}
|
|
19
|
+
return String(value);
|
|
20
|
+
}catch(_){return '';}
|
|
21
|
+
}
|
|
22
|
+
function stack(value){
|
|
23
|
+
try{return value&&value.stack?String(value.stack):'';}catch(_){return '';}
|
|
24
|
+
}
|
|
25
|
+
function updater(){
|
|
26
|
+
var cap=window.Capacitor;
|
|
27
|
+
if(!cap||!cap.Plugins){return null;}
|
|
28
|
+
return cap.Plugins.CapacitorUpdater||null;
|
|
29
|
+
}
|
|
30
|
+
function flush(){
|
|
31
|
+
var plugin=updater();
|
|
32
|
+
if(!plugin||typeof plugin.reportWebViewError!=='function'){return false;}
|
|
33
|
+
while(queue.length){
|
|
34
|
+
var payload=queue.shift();
|
|
35
|
+
try{
|
|
36
|
+
var result=plugin.reportWebViewError(payload);
|
|
37
|
+
if(result&&typeof result.catch==='function'){result.catch(function(){});}
|
|
38
|
+
}catch(_){}
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
var retries=0;
|
|
43
|
+
function scheduleFlush(){
|
|
44
|
+
if(flush()){return;}
|
|
45
|
+
if(retries++<40){setTimeout(scheduleFlush,250);}
|
|
46
|
+
}
|
|
47
|
+
function send(payload){
|
|
48
|
+
try{
|
|
49
|
+
if(sentReports>=maxReports){return;}
|
|
50
|
+
payload.href=payload.href||location.href||'';
|
|
51
|
+
payload.user_agent=navigator.userAgent||'';
|
|
52
|
+
payload.session_id=sessionId;
|
|
53
|
+
var key=[payload.type,payload.message,payload.source,payload.line,payload.column,payload.tag_name].join('|');
|
|
54
|
+
if(seen[key]){return;}
|
|
55
|
+
seen[key]=true;
|
|
56
|
+
sentReports+=1;
|
|
57
|
+
queue.push(payload);
|
|
58
|
+
scheduleFlush();
|
|
59
|
+
}catch(_){}
|
|
60
|
+
}
|
|
61
|
+
function readSession(){
|
|
62
|
+
try{return JSON.parse(localStorage.getItem(sessionKey)||'null')||null;}catch(_){return null;}
|
|
63
|
+
}
|
|
64
|
+
function writeSession(active){
|
|
65
|
+
try{
|
|
66
|
+
localStorage.setItem(sessionKey,JSON.stringify({
|
|
67
|
+
id:sessionId,
|
|
68
|
+
active:active,
|
|
69
|
+
href:location.href||'',
|
|
70
|
+
started_at:window.__capgoWebViewSessionStartedAt,
|
|
71
|
+
updated_at:String(Date.now())
|
|
72
|
+
}));
|
|
73
|
+
}catch(_){}
|
|
74
|
+
}
|
|
75
|
+
window.__capgoWebViewSessionStartedAt=String(Date.now());
|
|
76
|
+
var previous=readSession();
|
|
77
|
+
if(previous&&previous.active){
|
|
78
|
+
send({
|
|
79
|
+
type:'webview_unclean_restart',
|
|
80
|
+
message:'WebView restarted without a clean page unload',
|
|
81
|
+
previous_session_id:s(previous.id),
|
|
82
|
+
previous_href:s(previous.href),
|
|
83
|
+
previous_started_at:s(previous.started_at),
|
|
84
|
+
previous_updated_at:s(previous.updated_at)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
writeSession(true);
|
|
88
|
+
setInterval(function(){writeSession(true);},15000);
|
|
89
|
+
function markClean(){writeSession(false);}
|
|
90
|
+
window.addEventListener('pagehide',markClean,true);
|
|
91
|
+
window.addEventListener('beforeunload',markClean,true);
|
|
92
|
+
window.addEventListener('error',function(event){
|
|
93
|
+
var target=event&&event.target;
|
|
94
|
+
if(target&&target!==window&&(target.src||target.href)){
|
|
95
|
+
send({
|
|
96
|
+
type:'resource_error',
|
|
97
|
+
message:'Resource failed to load',
|
|
98
|
+
source:s(target.src||target.href),
|
|
99
|
+
tag_name:s(target.tagName)
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
send({
|
|
104
|
+
type:'javascript_error',
|
|
105
|
+
message:s((event&&event.message)||(event&&event.error)),
|
|
106
|
+
source:s(event&&event.filename),
|
|
107
|
+
line:s(event&&event.lineno),
|
|
108
|
+
column:s(event&&event.colno),
|
|
109
|
+
stack:stack(event&&event.error)
|
|
110
|
+
});
|
|
111
|
+
},true);
|
|
112
|
+
window.addEventListener('unhandledrejection',function(event){
|
|
113
|
+
var reason=event&&event.reason;
|
|
114
|
+
send({type:'unhandled_rejection',message:s(reason),stack:stack(reason)});
|
|
115
|
+
},true);
|
|
116
|
+
document.addEventListener('securitypolicyviolation',function(event){
|
|
117
|
+
send({
|
|
118
|
+
type:'security_policy_violation',
|
|
119
|
+
message:s(event&&event.violatedDirective),
|
|
120
|
+
source:s(event&&event.blockedURI)
|
|
121
|
+
});
|
|
122
|
+
},true);
|
|
123
|
+
document.addEventListener('deviceready',scheduleFlush,false);
|
|
124
|
+
setTimeout(scheduleFlush,0);
|
|
125
|
+
})();
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
private let implementation: CapgoUpdater
|
|
129
|
+
private var installed = false
|
|
130
|
+
|
|
131
|
+
init(implementation: CapgoUpdater) {
|
|
132
|
+
self.implementation = implementation
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func install(on webView: WKWebView?) {
|
|
136
|
+
guard !installed else {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
guard let webView else {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
installed = true
|
|
144
|
+
let userScript = WKUserScript(source: Self.script, injectionTime: .atDocumentStart, forMainFrameOnly: true)
|
|
145
|
+
webView.configuration.userContentController.addUserScript(userScript)
|
|
146
|
+
webView.evaluateJavaScript(Self.script, completionHandler: nil)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func reportError(_ call: CAPPluginCall) {
|
|
150
|
+
let errorType = call.getString("type") ?? "javascript_error"
|
|
151
|
+
let current = implementation.getCurrentBundle()
|
|
152
|
+
implementation.sendStats(
|
|
153
|
+
action: Self.statsAction(for: errorType),
|
|
154
|
+
versionName: current.getVersionName(),
|
|
155
|
+
oldVersionName: "",
|
|
156
|
+
metadata: Self.buildMetadata([
|
|
157
|
+
"type": errorType,
|
|
158
|
+
"message": call.getString("message"),
|
|
159
|
+
"source": call.getString("source"),
|
|
160
|
+
"line": call.getString("line") ?? call.getString("lineno"),
|
|
161
|
+
"column": call.getString("column") ?? call.getString("colno"),
|
|
162
|
+
"stack": call.getString("stack"),
|
|
163
|
+
"tag_name": call.getString("tag_name"),
|
|
164
|
+
"href": call.getString("href"),
|
|
165
|
+
"user_agent": call.getString("user_agent"),
|
|
166
|
+
"session_id": call.getString("session_id"),
|
|
167
|
+
"previous_session_id": call.getString("previous_session_id"),
|
|
168
|
+
"previous_href": call.getString("previous_href"),
|
|
169
|
+
"previous_started_at": call.getString("previous_started_at"),
|
|
170
|
+
"previous_updated_at": call.getString("previous_updated_at")
|
|
171
|
+
])
|
|
172
|
+
)
|
|
173
|
+
call.resolve()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static func statsAction(for type: String) -> String {
|
|
177
|
+
switch type {
|
|
178
|
+
case "unhandled_rejection":
|
|
179
|
+
return "webview_unhandled_rejection"
|
|
180
|
+
case "resource_error":
|
|
181
|
+
return "webview_resource_error"
|
|
182
|
+
case "security_policy_violation":
|
|
183
|
+
return "webview_security_policy_violation"
|
|
184
|
+
case "webview_unclean_restart":
|
|
185
|
+
return "webview_unclean_restart"
|
|
186
|
+
case "render_process_gone":
|
|
187
|
+
return "webview_render_process_gone"
|
|
188
|
+
case "web_content_process_terminated":
|
|
189
|
+
return "webview_content_process_terminated"
|
|
190
|
+
case "javascript_error":
|
|
191
|
+
return "webview_javascript_error"
|
|
192
|
+
default:
|
|
193
|
+
return "webview_javascript_error"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static func buildMetadata(_ values: [String: String?]) -> [String: String] {
|
|
198
|
+
var metadata: [String: String] = [:]
|
|
199
|
+
put(&metadata, key: "error_type", value: payloadValue(values, "type") ?? "javascript_error", maxLength: 64)
|
|
200
|
+
put(&metadata, key: "message", value: payloadValue(values, "message"), maxLength: 1_024)
|
|
201
|
+
put(&metadata, key: "source", value: sanitizeUrl(payloadValue(values, "source")), maxLength: 512)
|
|
202
|
+
put(&metadata, key: "line", value: payloadValue(values, "line"), maxLength: 32)
|
|
203
|
+
put(&metadata, key: "column", value: payloadValue(values, "column"), maxLength: 32)
|
|
204
|
+
put(&metadata, key: "stack", value: payloadValue(values, "stack"), maxLength: 2_048)
|
|
205
|
+
put(&metadata, key: "tag_name", value: payloadValue(values, "tag_name"), maxLength: 64)
|
|
206
|
+
put(&metadata, key: "href", value: sanitizeUrl(payloadValue(values, "href")), maxLength: 512)
|
|
207
|
+
put(&metadata, key: "user_agent", value: payloadValue(values, "user_agent"), maxLength: 256)
|
|
208
|
+
put(&metadata, key: "session_id", value: payloadValue(values, "session_id"), maxLength: 128)
|
|
209
|
+
put(&metadata, key: "previous_session_id", value: payloadValue(values, "previous_session_id"), maxLength: 128)
|
|
210
|
+
put(&metadata, key: "previous_href", value: sanitizeUrl(payloadValue(values, "previous_href")), maxLength: 512)
|
|
211
|
+
put(&metadata, key: "previous_started_at", value: payloadValue(values, "previous_started_at"), maxLength: 64)
|
|
212
|
+
put(&metadata, key: "previous_updated_at", value: payloadValue(values, "previous_updated_at"), maxLength: 64)
|
|
213
|
+
return metadata
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static func payloadValue(_ values: [String: String?], _ key: String) -> String? {
|
|
217
|
+
guard let value = values[key] else {
|
|
218
|
+
return nil
|
|
219
|
+
}
|
|
220
|
+
return value
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static func sanitizeUrl(_ value: String?) -> String? {
|
|
224
|
+
guard let value = value, !value.isEmpty else {
|
|
225
|
+
return value
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if var components = URLComponents(string: value), components.scheme != nil, components.host != nil {
|
|
229
|
+
components.user = nil
|
|
230
|
+
components.password = nil
|
|
231
|
+
components.query = nil
|
|
232
|
+
components.fragment = nil
|
|
233
|
+
components.path = sanitizeUrlPath(components.path)
|
|
234
|
+
return components.string ?? stripUrlQueryAndFragment(value)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return stripUrlQueryAndFragment(value)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private static func sanitizeUrlPath(_ path: String) -> String {
|
|
241
|
+
guard !path.isEmpty else {
|
|
242
|
+
return path
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return path
|
|
246
|
+
.split(separator: "/", omittingEmptySubsequences: false)
|
|
247
|
+
.map { isSensitiveUrlPathSegment(String($0)) ? "redacted" : String($0) }
|
|
248
|
+
.joined(separator: "/")
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private static func isSensitiveUrlPathSegment(_ segment: String) -> Bool {
|
|
252
|
+
segment.range(of: #"^[0-9]{6,}$"#, options: .regularExpression) != nil ||
|
|
253
|
+
segment.range(of: #"^[0-9a-fA-F]{16,}$"#, options: .regularExpression) != nil ||
|
|
254
|
+
segment.range(
|
|
255
|
+
of: #"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"#,
|
|
256
|
+
options: .regularExpression
|
|
257
|
+
) != nil
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static func stripUrlQueryAndFragment(_ value: String) -> String {
|
|
261
|
+
let queryIndex = value.firstIndex(of: "?")
|
|
262
|
+
let fragmentIndex = value.firstIndex(of: "#")
|
|
263
|
+
let endIndexes = [queryIndex, fragmentIndex].compactMap { $0 }
|
|
264
|
+
guard let endIndex = endIndexes.min() else {
|
|
265
|
+
return value
|
|
266
|
+
}
|
|
267
|
+
return String(value[..<endIndex])
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private static func put(_ metadata: inout [String: String], key: String, value: String?, maxLength: Int) {
|
|
271
|
+
guard let value = value, !value.isEmpty else {
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
metadata[key] = String(value.prefix(maxLength))
|
|
275
|
+
}
|
|
276
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.46.0",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -45,6 +45,15 @@
|
|
|
45
45
|
"verify:ios": "xcodebuild -scheme CapgoCapacitorUpdater -destination generic/platform=iOS",
|
|
46
46
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
47
47
|
"verify:web": "bun run build",
|
|
48
|
+
"native:compile": "./scripts/native-platform.sh compile all",
|
|
49
|
+
"native:compile:ios": "./scripts/native-platform.sh compile ios",
|
|
50
|
+
"native:compile:android": "./scripts/native-platform.sh compile android",
|
|
51
|
+
"native:test": "./scripts/native-platform.sh test all",
|
|
52
|
+
"native:test:ios": "./scripts/native-platform.sh test ios",
|
|
53
|
+
"native:test:android": "./scripts/native-platform.sh test android",
|
|
54
|
+
"native:contract": "bun run native:contract:ios && bun run native:contract:android",
|
|
55
|
+
"native:contract:ios": "./scripts/test-ios.sh -only-testing:CapacitorUpdaterPluginTests/NativeContractTests",
|
|
56
|
+
"native:contract:android": "cd android && ./gradlew testDebugUnitTest --tests ee.forgr.capacitor_updater.NativeContractTest && cd ..",
|
|
48
57
|
"test": "bun run test:ios && bun run test:android",
|
|
49
58
|
"test:ios": "./scripts/test-ios.sh",
|
|
50
59
|
"test:android": "cd android && ./gradlew test && cd ..",
|