@capgo/cordova-updater 8.1.1

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 (76) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +82 -0
  3. package/dist/esm/definitions.d.ts +2684 -0
  4. package/dist/esm/definitions.js +109 -0
  5. package/dist/esm/definitions.js.map +1 -0
  6. package/dist/esm/exec.d.ts +4 -0
  7. package/dist/esm/exec.js +25 -0
  8. package/dist/esm/exec.js.map +1 -0
  9. package/dist/esm/history.d.ts +1 -0
  10. package/dist/esm/history.js +283 -0
  11. package/dist/esm/history.js.map +1 -0
  12. package/dist/esm/index.d.ts +57 -0
  13. package/dist/esm/index.js +68 -0
  14. package/dist/esm/index.js.map +1 -0
  15. package/dist/esm/plugin.d.ts +57 -0
  16. package/dist/esm/plugin.js +15 -0
  17. package/dist/esm/plugin.js.map +1 -0
  18. package/dist/plugin.cjs.js +217 -0
  19. package/dist/plugin.cjs.js.map +1 -0
  20. package/dist/plugin.js +222 -0
  21. package/dist/plugin.js.map +1 -0
  22. package/package.json +96 -0
  23. package/plugin.xml +207 -0
  24. package/src/android/CordovaUpdaterPlugin.java +5662 -0
  25. package/src/android/UpdaterPathHandler.java +99 -0
  26. package/src/android/app/capgo/cordova/updater/AndroidAppExitReporter.java +92 -0
  27. package/src/android/app/capgo/cordova/updater/AppLifecycleObserver.java +115 -0
  28. package/src/android/app/capgo/cordova/updater/BundleInfo.java +246 -0
  29. package/src/android/app/capgo/cordova/updater/BundleStatus.java +45 -0
  30. package/src/android/app/capgo/cordova/updater/Callback.java +13 -0
  31. package/src/android/app/capgo/cordova/updater/CapgoUpdater.java +3404 -0
  32. package/src/android/app/capgo/cordova/updater/CordovaUpdaterConfig.java +56 -0
  33. package/src/android/app/capgo/cordova/updater/CryptoCipher.java +462 -0
  34. package/src/android/app/capgo/cordova/updater/DataManager.java +44 -0
  35. package/src/android/app/capgo/cordova/updater/DelayCondition.java +56 -0
  36. package/src/android/app/capgo/cordova/updater/DelayUntilNext.java +14 -0
  37. package/src/android/app/capgo/cordova/updater/DelayUpdateUtils.java +307 -0
  38. package/src/android/app/capgo/cordova/updater/DeviceIdHelper.java +230 -0
  39. package/src/android/app/capgo/cordova/updater/DownloadService.java +1219 -0
  40. package/src/android/app/capgo/cordova/updater/DownloadWorkerManager.java +260 -0
  41. package/src/android/app/capgo/cordova/updater/InternalUtils.java +54 -0
  42. package/src/android/app/capgo/cordova/updater/Logger.java +345 -0
  43. package/src/android/app/capgo/cordova/updater/ShakeDetector.java +72 -0
  44. package/src/android/app/capgo/cordova/updater/ShakeMenu.java +39 -0
  45. package/src/android/app/capgo/cordova/updater/ThreeFingerPinchDetector.java +323 -0
  46. package/src/android/app/capgo/cordova/updater/compat/JSArray.java +9 -0
  47. package/src/android/app/capgo/cordova/updater/compat/JSObject.java +83 -0
  48. package/src/android/app/capgo/cordova/updater/compat/PluginCall.java +165 -0
  49. package/src/android/capgo-cordova-updater.gradle +19 -0
  50. package/src/android/capgo-updater.xml +3 -0
  51. package/src/android/test/app/capgo/cordova/updater/CapacitorUpdaterUnitTest.java.skip +3039 -0
  52. package/src/android/test/app/capgo/cordova/updater/DelayUpdateUtilsTest.java +177 -0
  53. package/src/android/test/app/capgo/cordova/updater/NativeContractTest.java +105 -0
  54. package/src/ios/AES.swift +216 -0
  55. package/src/ios/AppHealthTracker.swift +82 -0
  56. package/src/ios/BundleInfo.swift +185 -0
  57. package/src/ios/BundleStatus.swift +127 -0
  58. package/src/ios/CapgoRawRsa.swift +36 -0
  59. package/src/ios/CapgoUpdater.swift +3544 -0
  60. package/src/ios/CordovaCompat.swift +11 -0
  61. package/src/ios/CordovaPluginCall.swift +190 -0
  62. package/src/ios/CordovaUpdaterCommands.swift +270 -0
  63. package/src/ios/CordovaUpdaterPlugin.swift +4706 -0
  64. package/src/ios/CryptoCipher.swift +326 -0
  65. package/src/ios/DelayCondition.swift +74 -0
  66. package/src/ios/DelayUntilNext.swift +30 -0
  67. package/src/ios/DelayUpdateUtils.swift +257 -0
  68. package/src/ios/DeviceIdHelper.swift +120 -0
  69. package/src/ios/Info.plist +28 -0
  70. package/src/ios/InternalUtils.swift +471 -0
  71. package/src/ios/Logger.swift +283 -0
  72. package/src/ios/RSA.swift +96 -0
  73. package/src/ios/ShakeMenu.swift +52 -0
  74. package/src/ios/UpdaterSchemeHandler.swift +81 -0
  75. package/src/ios/UserDefaultsExtension.swift +46 -0
  76. package/src/ios/WebViewStatsReporter.swift +303 -0
@@ -0,0 +1,99 @@
1
+ package app.capgo.cordova.updater;
2
+
3
+ import android.net.Uri;
4
+ import android.util.Log;
5
+ import android.webkit.MimeTypeMap;
6
+ import android.webkit.WebResourceResponse;
7
+ import androidx.annotation.NonNull;
8
+ import androidx.annotation.Nullable;
9
+ import androidx.webkit.WebViewAssetLoader;
10
+ import java.io.File;
11
+ import java.io.FileInputStream;
12
+ import java.io.IOException;
13
+ import java.io.InputStream;
14
+
15
+ public class UpdaterPathHandler implements WebViewAssetLoader.PathHandler {
16
+ public static final String TAG = "CapgoCordovaUpdater";
17
+
18
+ private final Object activeBundleDirLock = new Object();
19
+
20
+ @Nullable
21
+ private File activeBundleDir;
22
+
23
+ public void setActiveBundleDir(@Nullable File dir) {
24
+ synchronized (activeBundleDirLock) {
25
+ this.activeBundleDir = dir;
26
+ }
27
+ }
28
+
29
+ @Nullable
30
+ public File getActiveBundleDir() {
31
+ synchronized (activeBundleDirLock) {
32
+ return activeBundleDir;
33
+ }
34
+ }
35
+
36
+ @Override
37
+ @Nullable
38
+ public WebResourceResponse handle(@NonNull String path) {
39
+ File baseDir;
40
+ synchronized (activeBundleDirLock) {
41
+ baseDir = activeBundleDir;
42
+ }
43
+ if (baseDir == null) {
44
+ return null;
45
+ }
46
+
47
+ String resolvedPath = path;
48
+ if (resolvedPath.startsWith("/")) {
49
+ resolvedPath = resolvedPath.substring(1);
50
+ }
51
+ if (resolvedPath.isEmpty() || resolvedPath.endsWith("/")) {
52
+ resolvedPath = resolvedPath + "index.html";
53
+ }
54
+
55
+ File file = new File(baseDir, resolvedPath);
56
+ try {
57
+ String canonicalBase = baseDir.getCanonicalPath();
58
+ String canonicalFile = file.getCanonicalPath();
59
+ if (!canonicalFile.equals(canonicalBase) && !canonicalFile.startsWith(canonicalBase + File.separator)) {
60
+ return null;
61
+ }
62
+ } catch (IOException e) {
63
+ return null;
64
+ }
65
+
66
+ if (!file.isFile()) {
67
+ return null;
68
+ }
69
+
70
+ try {
71
+ InputStream stream = new FileInputStream(file);
72
+ String mimeType = guessMimeType(resolvedPath);
73
+ return new WebResourceResponse(mimeType, null, stream);
74
+ } catch (IOException e) {
75
+ Log.e(TAG, "Failed to open bundle file: " + file.getAbsolutePath(), e);
76
+ return null;
77
+ }
78
+ }
79
+
80
+ private String guessMimeType(@NonNull String path) {
81
+ if (path.endsWith(".js") || path.endsWith(".mjs")) {
82
+ return "application/javascript";
83
+ }
84
+ if (path.endsWith(".wasm")) {
85
+ return "application/wasm";
86
+ }
87
+ String extension = MimeTypeMap.getFileExtensionFromUrl(path);
88
+ if (extension != null) {
89
+ String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
90
+ if (mime != null) {
91
+ return mime;
92
+ }
93
+ }
94
+ if (path.endsWith(".html") || path.endsWith(".htm")) {
95
+ return "text/html";
96
+ }
97
+ return "application/octet-stream";
98
+ }
99
+ }
@@ -0,0 +1,92 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package app.capgo.cordova.updater;
8
+
9
+ import android.annotation.TargetApi;
10
+ import android.app.ActivityManager;
11
+ import android.app.ApplicationExitInfo;
12
+ import android.content.Context;
13
+ import android.content.SharedPreferences;
14
+ import android.os.Build;
15
+ import java.util.HashMap;
16
+ import java.util.List;
17
+ import java.util.Map;
18
+
19
+ @TargetApi(Build.VERSION_CODES.R)
20
+ final class AndroidAppExitReporter {
21
+
22
+ private AndroidAppExitReporter() {}
23
+
24
+ static void reportPreviousAppExitReasons(
25
+ final Context context,
26
+ final SharedPreferences prefs,
27
+ final CapgoUpdater implementation,
28
+ final Logger logger,
29
+ final String lastReportedAppExitTimestampPrefKey
30
+ ) {
31
+ try {
32
+ final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
33
+ if (activityManager == null) {
34
+ return;
35
+ }
36
+
37
+ final List<ApplicationExitInfo> exitReasons = activityManager.getHistoricalProcessExitReasons(context.getPackageName(), 0, 8);
38
+ if (exitReasons == null || exitReasons.isEmpty()) {
39
+ return;
40
+ }
41
+
42
+ final long lastReportedTimestamp = prefs.getLong(lastReportedAppExitTimestampPrefKey, 0L);
43
+ long newestReportedTimestamp = lastReportedTimestamp;
44
+ final BundleInfo current = implementation.getCurrentBundle();
45
+ final String versionName = current == null ? "" : current.getVersionName();
46
+
47
+ for (final ApplicationExitInfo exitInfo : exitReasons) {
48
+ if (exitInfo == null || exitInfo.getTimestamp() <= lastReportedTimestamp) {
49
+ continue;
50
+ }
51
+
52
+ final String action = CordovaUpdaterPlugin.statsActionForApplicationExitReason(exitInfo.getReason());
53
+ if (action == null) {
54
+ continue;
55
+ }
56
+
57
+ implementation.sendStats(action, versionName, "", buildApplicationExitMetadata(exitInfo));
58
+ newestReportedTimestamp = Math.max(newestReportedTimestamp, exitInfo.getTimestamp());
59
+ }
60
+
61
+ if (newestReportedTimestamp > lastReportedTimestamp) {
62
+ prefs.edit().putLong(lastReportedAppExitTimestampPrefKey, newestReportedTimestamp).apply();
63
+ }
64
+ } catch (final Exception e) {
65
+ logger.warn("Unable to report previous app exit reason: " + e.getMessage());
66
+ }
67
+ }
68
+
69
+ private static Map<String, String> buildApplicationExitMetadata(final ApplicationExitInfo exitInfo) {
70
+ final Map<String, String> metadata = new HashMap<>();
71
+ metadata.put("exit_reason", CordovaUpdaterPlugin.applicationExitReasonName(exitInfo.getReason()));
72
+ metadata.put("exit_reason_code", Integer.toString(exitInfo.getReason()));
73
+ metadata.put("exit_status", Integer.toString(exitInfo.getStatus()));
74
+ metadata.put("exit_importance", Integer.toString(exitInfo.getImportance()));
75
+ metadata.put("exit_timestamp", Long.toString(exitInfo.getTimestamp()));
76
+ metadata.put("pid", Integer.toString(exitInfo.getPid()));
77
+ metadata.put("pss_kb", Long.toString(exitInfo.getPss()));
78
+ metadata.put("rss_kb", Long.toString(exitInfo.getRss()));
79
+
80
+ final String processName = exitInfo.getProcessName();
81
+ if (processName != null && !processName.isEmpty()) {
82
+ metadata.put("process_name", CordovaUpdaterPlugin.truncateStatsMetadataValue(processName, 128));
83
+ }
84
+
85
+ final String description = exitInfo.getDescription();
86
+ if (description != null && !description.isEmpty()) {
87
+ metadata.put("exit_description", CordovaUpdaterPlugin.truncateStatsMetadataValue(description, 512));
88
+ }
89
+
90
+ return metadata;
91
+ }
92
+ }
@@ -0,0 +1,115 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package app.capgo.cordova.updater;
8
+
9
+ import android.content.Context;
10
+ import androidx.annotation.NonNull;
11
+ import androidx.lifecycle.DefaultLifecycleObserver;
12
+ import androidx.lifecycle.LifecycleOwner;
13
+ import androidx.lifecycle.ProcessLifecycleInitializer;
14
+ import androidx.lifecycle.ProcessLifecycleOwner;
15
+ import androidx.startup.AppInitializer;
16
+
17
+ /**
18
+ * Observes app-level lifecycle events using ProcessLifecycleOwner.
19
+ * This provides reliable detection of when the entire app (not just an activity)
20
+ * moves to foreground or background.
21
+ *
22
+ * Unlike activity lifecycle callbacks, this won't trigger false positives when:
23
+ * - Opening a native camera view
24
+ * - Opening share sheets
25
+ * - Any other native activity overlays within the app
26
+ *
27
+ * The ON_STOP event is dispatched with a ~700ms delay after the last activity
28
+ * passes through onStop(), which helps avoid false triggers during configuration
29
+ * changes or quick activity transitions.
30
+ */
31
+ public class AppLifecycleObserver implements DefaultLifecycleObserver {
32
+
33
+ public interface AppLifecycleListener {
34
+ void onAppMovedToForeground();
35
+ void onAppMovedToBackground();
36
+ }
37
+
38
+ private final AppLifecycleListener listener;
39
+ private final Logger logger;
40
+ private boolean isRegistered = false;
41
+
42
+ public AppLifecycleObserver(AppLifecycleListener listener, Logger logger) {
43
+ this.listener = listener;
44
+ this.logger = logger;
45
+ }
46
+
47
+ public boolean isRegistered() {
48
+ return isRegistered;
49
+ }
50
+
51
+ public void register(Context context) {
52
+ if (isRegistered) {
53
+ return;
54
+ }
55
+ if (context == null) {
56
+ logger.error("Cannot register AppLifecycleObserver without context");
57
+ return;
58
+ }
59
+ // get()/addObserver() succeed even when ProcessLifecycleOwner.init() never ran.
60
+ // Initialize first; if that fails, leave isRegistered false so activity fallback runs.
61
+ try {
62
+ AppInitializer.getInstance(context.getApplicationContext()).initializeComponent(ProcessLifecycleInitializer.class);
63
+ } catch (Exception e) {
64
+ logger.error("Failed to initialize ProcessLifecycleOwner: " + e.getMessage());
65
+ return;
66
+ }
67
+ if (!addObserver()) {
68
+ logger.error("Failed to register AppLifecycleObserver with ProcessLifecycleOwner");
69
+ }
70
+ }
71
+
72
+ private boolean addObserver() {
73
+ try {
74
+ ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
75
+ isRegistered = true;
76
+ logger.info("AppLifecycleObserver registered with ProcessLifecycleOwner");
77
+ return true;
78
+ } catch (Exception e) {
79
+ logger.error("Failed to add AppLifecycleObserver: " + e.getMessage());
80
+ return false;
81
+ }
82
+ }
83
+
84
+ public void unregister() {
85
+ if (!isRegistered) {
86
+ return;
87
+ }
88
+ try {
89
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
90
+ isRegistered = false;
91
+ logger.info("AppLifecycleObserver unregistered from ProcessLifecycleOwner");
92
+ } catch (Exception e) {
93
+ logger.error("Failed to unregister AppLifecycleObserver: " + e.getMessage());
94
+ }
95
+ }
96
+
97
+ @Override
98
+ public void onStart(@NonNull LifecycleOwner owner) {
99
+ // App moved to foreground (at least one activity is visible)
100
+ logger.info("ProcessLifecycleOwner: App moved to foreground");
101
+ if (listener != null) {
102
+ listener.onAppMovedToForeground();
103
+ }
104
+ }
105
+
106
+ @Override
107
+ public void onStop(@NonNull LifecycleOwner owner) {
108
+ // App moved to background (no activities are visible)
109
+ // Note: This is called with a ~700ms delay to avoid false triggers
110
+ logger.info("ProcessLifecycleOwner: App moved to background");
111
+ if (listener != null) {
112
+ listener.onAppMovedToBackground();
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,246 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package app.capgo.cordova.updater;
8
+
9
+ import java.text.SimpleDateFormat;
10
+ import java.util.Date;
11
+ import java.util.HashMap;
12
+ import java.util.Map;
13
+ import java.util.Objects;
14
+ import java.util.TimeZone;
15
+ import org.json.JSONException;
16
+ import org.json.JSONObject;
17
+ import org.json.JSONTokener;
18
+
19
+ public class BundleInfo {
20
+
21
+ private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
22
+
23
+ public static final String ID_BUILTIN = "builtin";
24
+ public static final String VERSION_UNKNOWN = "unknown";
25
+ public static final String DOWNLOADED_BUILTIN = "1970-01-01T00:00:00.000Z";
26
+
27
+ private final String downloaded;
28
+ private final String id;
29
+ private final String version;
30
+ private final String checksum;
31
+ private final BundleStatus status;
32
+ private final String link;
33
+ private final String comment;
34
+
35
+ static {
36
+ sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
37
+ }
38
+
39
+ public BundleInfo(final BundleInfo source) {
40
+ this(source.id, source.version, source.status, source.downloaded, source.checksum, source.link, source.comment);
41
+ }
42
+
43
+ public BundleInfo(final String id, final String version, final BundleStatus status, final Date downloaded, final String checksum) {
44
+ this(id, version, status, sdf.format(downloaded), checksum, null, null);
45
+ }
46
+
47
+ public BundleInfo(
48
+ final String id,
49
+ final String version,
50
+ final BundleStatus status,
51
+ final Date downloaded,
52
+ final String checksum,
53
+ final String link,
54
+ final String comment
55
+ ) {
56
+ this(id, version, status, sdf.format(downloaded), checksum, link, comment);
57
+ }
58
+
59
+ public BundleInfo(final String id, final String version, final BundleStatus status, final String downloaded, final String checksum) {
60
+ this(id, version, status, downloaded, checksum, null, null);
61
+ }
62
+
63
+ public BundleInfo(
64
+ final String id,
65
+ final String version,
66
+ final BundleStatus status,
67
+ final String downloaded,
68
+ final String checksum,
69
+ final String link,
70
+ final String comment
71
+ ) {
72
+ this.downloaded = downloaded != null ? downloaded.trim() : "";
73
+ this.id = id != null ? id : "";
74
+ this.version = version;
75
+ this.checksum = checksum != null ? checksum : "";
76
+ this.status = status != null ? status : BundleStatus.ERROR;
77
+ this.link = link;
78
+ this.comment = comment;
79
+ }
80
+
81
+ public Boolean isBuiltin() {
82
+ return ID_BUILTIN.equals(this.id);
83
+ }
84
+
85
+ public Boolean isUnknown() {
86
+ return VERSION_UNKNOWN.equals(this.id);
87
+ }
88
+
89
+ public Boolean isErrorStatus() {
90
+ return BundleStatus.ERROR == this.status;
91
+ }
92
+
93
+ public Boolean isDeleted() {
94
+ return BundleStatus.DELETED == this.status;
95
+ }
96
+
97
+ public Boolean isDeleting() {
98
+ return BundleStatus.DELETING == this.status;
99
+ }
100
+
101
+ public boolean isDownloaded() {
102
+ return !this.isBuiltin() && this.downloaded != null && !this.downloaded.isEmpty() && !this.isDeleted() && !this.isDeleting();
103
+ }
104
+
105
+ public String getDownloaded() {
106
+ return this.isBuiltin() ? DOWNLOADED_BUILTIN : this.downloaded != null ? this.downloaded : "";
107
+ }
108
+
109
+ public BundleInfo setDownloaded(Date downloaded) {
110
+ return new BundleInfo(this.id, this.version, this.status, downloaded, this.checksum, this.link, this.comment);
111
+ }
112
+
113
+ public String getChecksum() {
114
+ return this.isBuiltin() ? "" : this.checksum != null ? this.checksum : "";
115
+ }
116
+
117
+ public BundleInfo setChecksum(String checksum) {
118
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, checksum, this.link, this.comment);
119
+ }
120
+
121
+ public String getId() {
122
+ return this.isBuiltin() ? ID_BUILTIN : this.id;
123
+ }
124
+
125
+ public BundleInfo setId(String id) {
126
+ return new BundleInfo(id, this.version, this.status, this.downloaded, this.checksum, this.link, this.comment);
127
+ }
128
+
129
+ public String getVersionName() {
130
+ return this.version == null ? ID_BUILTIN : this.version;
131
+ }
132
+
133
+ public BundleInfo setVersionName(String version) {
134
+ return new BundleInfo(this.id, version, this.status, this.downloaded, this.checksum, this.link, this.comment);
135
+ }
136
+
137
+ public BundleStatus getStatus() {
138
+ if (this.isBuiltin()) {
139
+ return BundleStatus.SUCCESS;
140
+ }
141
+ return this.status != null ? this.status : BundleStatus.ERROR;
142
+ }
143
+
144
+ public BundleInfo setStatus(BundleStatus status) {
145
+ return new BundleInfo(this.id, this.version, status, this.downloaded, this.checksum, this.link, this.comment);
146
+ }
147
+
148
+ public String getLink() {
149
+ return this.link;
150
+ }
151
+
152
+ public BundleInfo setLink(String link) {
153
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, this.checksum, link, this.comment);
154
+ }
155
+
156
+ public String getComment() {
157
+ return this.comment;
158
+ }
159
+
160
+ public BundleInfo setComment(String comment) {
161
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, this.checksum, this.link, comment);
162
+ }
163
+
164
+ public static BundleInfo fromJSON(final String jsonString) throws JSONException {
165
+ JSONObject json = new JSONObject(new JSONTokener(jsonString));
166
+ return new BundleInfo(
167
+ json.has("id") ? json.getString("id") : "",
168
+ json.has("version") ? json.getString("version") : BundleInfo.VERSION_UNKNOWN,
169
+ json.has("status") ? BundleStatus.fromString(json.getString("status")) : BundleStatus.PENDING,
170
+ json.has("downloaded") ? json.getString("downloaded") : "",
171
+ json.has("checksum") ? json.getString("checksum") : "",
172
+ json.has("link") ? json.getString("link") : null,
173
+ json.has("comment") ? json.getString("comment") : null
174
+ );
175
+ }
176
+
177
+ public Map<String, Object> toJSONMap() {
178
+ final Map<String, Object> result = new HashMap<>();
179
+ result.put("id", this.getId());
180
+ result.put("version", this.getVersionName());
181
+ result.put("downloaded", this.getDownloaded());
182
+ result.put("checksum", this.getChecksum());
183
+ result.put("status", this.getStatus().toString());
184
+ if (this.link != null && !this.link.isEmpty()) {
185
+ result.put("link", this.link);
186
+ }
187
+ if (this.comment != null && !this.comment.isEmpty()) {
188
+ result.put("comment", this.comment);
189
+ }
190
+ return result;
191
+ }
192
+
193
+ @Override
194
+ public boolean equals(final Object o) {
195
+ if (this == o) return true;
196
+ if (!(o instanceof BundleInfo)) return false;
197
+ final BundleInfo that = (BundleInfo) o;
198
+ return this.getId().equals(that.getId());
199
+ }
200
+
201
+ @Override
202
+ public int hashCode() {
203
+ return Objects.hash(this.version);
204
+ }
205
+
206
+ @Override
207
+ public String toString() {
208
+ try {
209
+ // Build JSON manually with extra safety checks
210
+ StringBuilder json = new StringBuilder();
211
+ json.append("{");
212
+
213
+ // Safe ID access
214
+ String safeId = this.id != null ? this.id : "";
215
+ if (this.isBuiltin()) safeId = ID_BUILTIN;
216
+ json.append("\"id\":\"").append(safeId).append("\",");
217
+
218
+ // Safe version access
219
+ String safeVersion = this.version != null ? this.version : ID_BUILTIN;
220
+ json.append("\"version\":\"").append(safeVersion).append("\",");
221
+
222
+ // Safe downloaded access
223
+ String safeDownloaded = this.downloaded != null ? this.downloaded : "";
224
+ if (this.isBuiltin()) safeDownloaded = DOWNLOADED_BUILTIN;
225
+ json.append("\"downloaded\":\"").append(safeDownloaded).append("\",");
226
+
227
+ // Safe checksum access
228
+ String safeChecksum = this.checksum != null ? this.checksum : "";
229
+ json.append("\"checksum\":\"").append(safeChecksum).append("\",");
230
+
231
+ // Safe status access
232
+ BundleStatus safeStatus = this.status != null ? this.status : BundleStatus.ERROR;
233
+ if (this.isBuiltin()) safeStatus = BundleStatus.SUCCESS;
234
+ json.append("\"status\":\"").append(safeStatus.toString()).append("\"");
235
+
236
+ json.append("}");
237
+ return json.toString();
238
+ } catch (Exception e) {
239
+ // Log the error for debugging but still return valid JSON
240
+ System.err.println("BundleInfo toString() error: " + e.getMessage());
241
+ e.printStackTrace();
242
+ // Return absolute minimal JSON
243
+ return "{\"id\":\"" + (this.id != null ? this.id : "unknown") + "\",\"status\":\"error\"}";
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,45 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package app.capgo.cordova.updater;
8
+
9
+ import java.util.HashMap;
10
+ import java.util.Map;
11
+
12
+ public enum BundleStatus {
13
+ SUCCESS("success"),
14
+ ERROR("error"),
15
+ PENDING("pending"),
16
+ DELETED("deleted"),
17
+ DELETING("deleting"),
18
+ DOWNLOADING("downloading");
19
+
20
+ public final String label;
21
+
22
+ private static final Map<String, BundleStatus> BY_LABEL = new HashMap<>();
23
+
24
+ static {
25
+ for (final BundleStatus e : values()) {
26
+ BY_LABEL.put(e.label, e);
27
+ }
28
+ }
29
+
30
+ BundleStatus(final String label) {
31
+ this.label = label;
32
+ }
33
+
34
+ @Override
35
+ public String toString() {
36
+ return this.label;
37
+ }
38
+
39
+ public static BundleStatus fromString(final String status) {
40
+ if (status == null || status.isEmpty()) {
41
+ return BundleStatus.PENDING;
42
+ }
43
+ return BundleStatus.BY_LABEL.get(status);
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package app.capgo.cordova.updater;
8
+
9
+ import java.util.Map;
10
+
11
+ public interface Callback {
12
+ void callback(Map<String, Object> result);
13
+ }