@capgo/capacitor-updater 7.45.10 → 7.50.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.
Files changed (33) hide show
  1. package/Package.swift +1 -1
  2. package/README.md +510 -92
  3. package/android/src/main/java/ee/forgr/capacitor_updater/AndroidAppExitReporter.java +92 -0
  4. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +3192 -777
  5. package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +798 -299
  6. package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +45 -30
  7. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +46 -28
  8. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +2 -0
  9. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +3 -3
  10. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +359 -25
  11. package/android/src/main/java/ee/forgr/capacitor_updater/ThreeFingerPinchDetector.java +323 -0
  12. package/dist/docs.json +1283 -169
  13. package/dist/esm/definitions.d.ts +621 -44
  14. package/dist/esm/definitions.js.map +1 -1
  15. package/dist/esm/web.d.ts +11 -1
  16. package/dist/esm/web.js +59 -1
  17. package/dist/esm/web.js.map +1 -1
  18. package/dist/plugin.cjs.js +59 -1
  19. package/dist/plugin.cjs.js.map +1 -1
  20. package/dist/plugin.js +59 -1
  21. package/dist/plugin.js.map +1 -1
  22. package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +0 -1
  23. package/ios/Sources/CapacitorUpdaterPlugin/AppHealthTracker.swift +82 -0
  24. package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +0 -16
  25. package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +2 -2
  26. package/ios/Sources/CapacitorUpdaterPlugin/BundleStatus.swift +78 -2
  27. package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +2116 -223
  28. package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +997 -332
  29. package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +0 -1
  30. package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +78 -1
  31. package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +418 -36
  32. package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +276 -0
  33. package/package.json +15 -3
@@ -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": "7.45.10",
3
+ "version": "7.50.2",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",
@@ -45,12 +45,22 @@
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 ..",
51
60
  "test:maestro": "./scripts/maestro/run-android-live-update.sh",
52
61
  "test:maestro:android": "./scripts/test-maestro-android.sh",
53
- "test:maestro:ios": "./scripts/test-maestro-ios.sh",
62
+ "test:maestro:ios": "./scripts/maestro/run-ios-live-update.sh",
63
+ "test:maestro:ios:smoke": "./scripts/test-maestro-ios.sh",
54
64
  "lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
55
65
  "fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
56
66
  "eslint": "eslint . --ext .ts",
@@ -61,7 +71,9 @@
61
71
  "clean": "rimraf ./dist",
62
72
  "watch": "tsc --watch",
63
73
  "prepublishOnly": "bun run build",
64
- "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
74
+ "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
75
+ "example:install": "cd example-app && bun install --frozen-lockfile",
76
+ "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build"
65
77
  },
66
78
  "devDependencies": {
67
79
  "@capacitor/android": "^7.4.3",