@capgo/capacitor-updater 7.20.1 → 7.22.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/README.md +83 -14
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +155 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +61 -47
- package/dist/docs.json +146 -2
- package/dist/esm/definitions.d.ts +50 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -1
- package/dist/esm/web.js +8 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +8 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +8 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +116 -15
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +3 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -255,6 +255,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
255
255
|
| **`localApiFiles`** | <code>string</code> | Configure the CLI to use a local file api for testing. | <code>undefined</code> | 6.3.3 |
|
|
256
256
|
| **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
|
|
257
257
|
| **`allowModifyAppId`** | <code>boolean</code> | Allow the plugin to modify the appId dynamically from the JavaScript side. | <code>false</code> | 7.14.0 |
|
|
258
|
+
| **`allowManualBundleError`** | <code>boolean</code> | Allow marking bundles as errored from JavaScript while using manual update flows. When enabled, {@link CapacitorUpdaterPlugin.setBundleError} can change a bundle status to `error`. | <code>false</code> | 7.20.0 |
|
|
258
259
|
| **`persistCustomId`** | <code>boolean</code> | Persist the customId set through {@link CapacitorUpdaterPlugin.setCustomId} across app restarts. Only available for Android and iOS. | <code>false (will be true by default in a future major release v8.x.x)</code> | 7.17.3 |
|
|
259
260
|
| **`persistModifyUrl`** | <code>boolean</code> | Persist the updateUrl, statsUrl and channelUrl set through {@link CapacitorUpdaterPlugin.setUpdateUrl}, {@link CapacitorUpdaterPlugin.setStatsUrl} and {@link CapacitorUpdaterPlugin.setChannelUrl} across app restarts. Only available for Android and iOS. | <code>false</code> | 7.20.0 |
|
|
260
261
|
| **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. Case sensitive. This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud. This requires the channel to allow devices to self dissociate/associate in the channel settings. https://capgo.app/docs/public-api/channels/#channel-configuration-options | <code>undefined</code> | 5.5.0 |
|
|
@@ -296,6 +297,7 @@ In `capacitor.config.json`:
|
|
|
296
297
|
"localApiFiles": undefined,
|
|
297
298
|
"allowModifyUrl": undefined,
|
|
298
299
|
"allowModifyAppId": undefined,
|
|
300
|
+
"allowManualBundleError": undefined,
|
|
299
301
|
"persistCustomId": undefined,
|
|
300
302
|
"persistModifyUrl": undefined,
|
|
301
303
|
"defaultChannel": undefined,
|
|
@@ -343,6 +345,7 @@ const config: CapacitorConfig = {
|
|
|
343
345
|
localApiFiles: undefined,
|
|
344
346
|
allowModifyUrl: undefined,
|
|
345
347
|
allowModifyAppId: undefined,
|
|
348
|
+
allowManualBundleError: undefined,
|
|
346
349
|
persistCustomId: undefined,
|
|
347
350
|
persistModifyUrl: undefined,
|
|
348
351
|
defaultChannel: undefined,
|
|
@@ -371,6 +374,7 @@ export default config;
|
|
|
371
374
|
* [`next(...)`](#next)
|
|
372
375
|
* [`set(...)`](#set)
|
|
373
376
|
* [`delete(...)`](#delete)
|
|
377
|
+
* [`setBundleError(...)`](#setbundleerror)
|
|
374
378
|
* [`list(...)`](#list)
|
|
375
379
|
* [`reset(...)`](#reset)
|
|
376
380
|
* [`current()`](#current)
|
|
@@ -392,6 +396,7 @@ export default config;
|
|
|
392
396
|
* [`addListener('noNeedUpdate', ...)`](#addlistenernoneedupdate-)
|
|
393
397
|
* [`addListener('updateAvailable', ...)`](#addlistenerupdateavailable-)
|
|
394
398
|
* [`addListener('downloadComplete', ...)`](#addlistenerdownloadcomplete-)
|
|
399
|
+
* [`addListener('breakingAvailable', ...)`](#addlistenerbreakingavailable-)
|
|
395
400
|
* [`addListener('majorAvailable', ...)`](#addlistenermajoravailable-)
|
|
396
401
|
* [`addListener('updateFailed', ...)`](#addlistenerupdatefailed-)
|
|
397
402
|
* [`addListener('downloadFailed', ...)`](#addlistenerdownloadfailed-)
|
|
@@ -399,6 +404,7 @@ export default config;
|
|
|
399
404
|
* [`addListener('appReady', ...)`](#addlistenerappready-)
|
|
400
405
|
* [`isAutoUpdateAvailable()`](#isautoupdateavailable)
|
|
401
406
|
* [`getNextBundle()`](#getnextbundle)
|
|
407
|
+
* [`getFailedUpdate()`](#getfailedupdate)
|
|
402
408
|
* [`setShakeMenu(...)`](#setshakemenu)
|
|
403
409
|
* [`isShakeMenuEnabled()`](#isshakemenuenabled)
|
|
404
410
|
* [`getAppId()`](#getappid)
|
|
@@ -541,6 +547,25 @@ Deletes the specified bundle from the native app storage. Use with {@link list}
|
|
|
541
547
|
--------------------
|
|
542
548
|
|
|
543
549
|
|
|
550
|
+
### setBundleError(...)
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
setBundleError(options: BundleId) => Promise<BundleInfo>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Mark an installed bundle as errored. Only available when {@link PluginsConfig.CapacitorUpdater.allowManualBundleError} is true.
|
|
557
|
+
|
|
558
|
+
| Param | Type | Description |
|
|
559
|
+
| ------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
560
|
+
| **`options`** | <code><a href="#bundleid">BundleId</a></code> | A {@link <a href="#bundleid">BundleId</a>} object containing the bundle id to mark as errored. |
|
|
561
|
+
|
|
562
|
+
**Returns:** <code>Promise<<a href="#bundleinfo">BundleInfo</a>></code>
|
|
563
|
+
|
|
564
|
+
**Since:** 7.20.0
|
|
565
|
+
|
|
566
|
+
--------------------
|
|
567
|
+
|
|
568
|
+
|
|
544
569
|
### list(...)
|
|
545
570
|
|
|
546
571
|
```typescript
|
|
@@ -890,6 +915,27 @@ Listen for downloadComplete events.
|
|
|
890
915
|
--------------------
|
|
891
916
|
|
|
892
917
|
|
|
918
|
+
### addListener('breakingAvailable', ...)
|
|
919
|
+
|
|
920
|
+
```typescript
|
|
921
|
+
addListener(eventName: 'breakingAvailable', listenerFunc: (state: BreakingAvailableEvent) => void) => Promise<PluginListenerHandle>
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
Listen for breaking update events when the backend flags an update as incompatible with the current app.
|
|
925
|
+
Emits the same payload as the legacy `majorAvailable` listener.
|
|
926
|
+
|
|
927
|
+
| Param | Type |
|
|
928
|
+
| ------------------ | --------------------------------------------------------------------------------------- |
|
|
929
|
+
| **`eventName`** | <code>'breakingAvailable'</code> |
|
|
930
|
+
| **`listenerFunc`** | <code>(state: <a href="#majoravailableevent">MajorAvailableEvent</a>) => void</code> |
|
|
931
|
+
|
|
932
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
933
|
+
|
|
934
|
+
**Since:** 7.22.0
|
|
935
|
+
|
|
936
|
+
--------------------
|
|
937
|
+
|
|
938
|
+
|
|
893
939
|
### addListener('majorAvailable', ...)
|
|
894
940
|
|
|
895
941
|
```typescript
|
|
@@ -1019,6 +1065,21 @@ Returns null if no next bundle is set.
|
|
|
1019
1065
|
--------------------
|
|
1020
1066
|
|
|
1021
1067
|
|
|
1068
|
+
### getFailedUpdate()
|
|
1069
|
+
|
|
1070
|
+
```typescript
|
|
1071
|
+
getFailedUpdate() => Promise<UpdateFailedEvent | null>
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
Get the most recent update that failed to install, if any. The stored value is cleared after it is retrieved once.
|
|
1075
|
+
|
|
1076
|
+
**Returns:** <code>Promise<<a href="#updatefailedevent">UpdateFailedEvent</a> | null></code>
|
|
1077
|
+
|
|
1078
|
+
**Since:** 7.22.0
|
|
1079
|
+
|
|
1080
|
+
--------------------
|
|
1081
|
+
|
|
1082
|
+
|
|
1022
1083
|
### setShakeMenu(...)
|
|
1023
1084
|
|
|
1024
1085
|
```typescript
|
|
@@ -1201,17 +1262,18 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
1201
1262
|
|
|
1202
1263
|
#### LatestVersion
|
|
1203
1264
|
|
|
1204
|
-
| Prop | Type | Description
|
|
1205
|
-
| ---------------- | ---------------------------- |
|
|
1206
|
-
| **`version`** | <code>string</code> | Result of getLatest method
|
|
1207
|
-
| **`checksum`** | <code>string</code> |
|
|
1208
|
-
| **`
|
|
1209
|
-
| **`
|
|
1210
|
-
| **`
|
|
1211
|
-
| **`
|
|
1212
|
-
| **`
|
|
1213
|
-
| **`
|
|
1214
|
-
| **`
|
|
1265
|
+
| Prop | Type | Description | Since |
|
|
1266
|
+
| ---------------- | ---------------------------- | -------------------------------------------------------------------- | ------ |
|
|
1267
|
+
| **`version`** | <code>string</code> | Result of getLatest method | 4.0.0 |
|
|
1268
|
+
| **`checksum`** | <code>string</code> | | 6 |
|
|
1269
|
+
| **`breaking`** | <code>boolean</code> | Indicates whether the update was flagged as breaking by the backend. | 7.22.0 |
|
|
1270
|
+
| **`major`** | <code>boolean</code> | | |
|
|
1271
|
+
| **`message`** | <code>string</code> | | |
|
|
1272
|
+
| **`sessionKey`** | <code>string</code> | | |
|
|
1273
|
+
| **`error`** | <code>string</code> | | |
|
|
1274
|
+
| **`old`** | <code>string</code> | | |
|
|
1275
|
+
| **`url`** | <code>string</code> | | |
|
|
1276
|
+
| **`manifest`** | <code>ManifestEntry[]</code> | | 6.1 |
|
|
1215
1277
|
|
|
1216
1278
|
|
|
1217
1279
|
#### GetLatestOptions
|
|
@@ -1346,9 +1408,9 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
1346
1408
|
|
|
1347
1409
|
#### MajorAvailableEvent
|
|
1348
1410
|
|
|
1349
|
-
| Prop | Type | Description
|
|
1350
|
-
| ------------- | ------------------- |
|
|
1351
|
-
| **`version`** | <code>string</code> | Emit when a
|
|
1411
|
+
| Prop | Type | Description | Since |
|
|
1412
|
+
| ------------- | ------------------- | ----------------------------------------- | ----- |
|
|
1413
|
+
| **`version`** | <code>string</code> | Emit when a breaking update is available. | 4.0.0 |
|
|
1352
1414
|
|
|
1353
1415
|
|
|
1354
1416
|
#### UpdateFailedEvent
|
|
@@ -1425,6 +1487,13 @@ error: The bundle has failed to download.
|
|
|
1425
1487
|
|
|
1426
1488
|
<code>'background' | 'kill' | 'nativeVersion' | 'date'</code>
|
|
1427
1489
|
|
|
1490
|
+
|
|
1491
|
+
#### BreakingAvailableEvent
|
|
1492
|
+
|
|
1493
|
+
Payload emitted by {@link CapacitorUpdaterPlugin.addListener} with `breakingAvailable`.
|
|
1494
|
+
|
|
1495
|
+
<code><a href="#majoravailableevent">MajorAvailableEvent</a></code>
|
|
1496
|
+
|
|
1428
1497
|
</docgen-api>
|
|
1429
1498
|
|
|
1430
1499
|
### Listen to download events
|
|
@@ -68,8 +68,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
68
68
|
private static final String UPDATE_URL_PREF_KEY = "CapacitorUpdater.updateUrl";
|
|
69
69
|
private static final String STATS_URL_PREF_KEY = "CapacitorUpdater.statsUrl";
|
|
70
70
|
private static final String CHANNEL_URL_PREF_KEY = "CapacitorUpdater.channelUrl";
|
|
71
|
+
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
72
|
+
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
71
73
|
|
|
72
|
-
private final String PLUGIN_VERSION = "7.
|
|
74
|
+
private final String PLUGIN_VERSION = "7.22.0";
|
|
73
75
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
74
76
|
|
|
75
77
|
private SharedPreferences.Editor editor;
|
|
@@ -97,6 +99,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
97
99
|
private String directUpdateMode = "false";
|
|
98
100
|
private Boolean wasRecentlyInstalledOrUpdated = false;
|
|
99
101
|
Boolean shakeMenuEnabled = false;
|
|
102
|
+
private Boolean allowManualBundleError = false;
|
|
100
103
|
|
|
101
104
|
private Boolean isPreviousMainActivity = true;
|
|
102
105
|
|
|
@@ -115,6 +118,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
115
118
|
private FrameLayout splashscreenLoaderOverlay;
|
|
116
119
|
private Runnable splashscreenTimeoutRunnable;
|
|
117
120
|
|
|
121
|
+
private void notifyBreakingEvents(final String version) {
|
|
122
|
+
if (version == null || version.isEmpty()) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (final String eventName : BREAKING_EVENT_NAMES) {
|
|
126
|
+
final JSObject payload = new JSObject();
|
|
127
|
+
payload.put("version", version);
|
|
128
|
+
CapacitorUpdaterPlugin.this.notifyListeners(eventName, payload);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
118
132
|
private JSObject mapToJSObject(Map<String, Object> map) {
|
|
119
133
|
JSObject jsObject = new JSObject();
|
|
120
134
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
@@ -123,6 +137,37 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
123
137
|
return jsObject;
|
|
124
138
|
}
|
|
125
139
|
|
|
140
|
+
private void persistLastFailedBundle(BundleInfo bundle) {
|
|
141
|
+
if (this.prefs == null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
final SharedPreferences.Editor localEditor = this.prefs.edit();
|
|
145
|
+
if (bundle == null) {
|
|
146
|
+
localEditor.remove(LAST_FAILED_BUNDLE_PREF_KEY);
|
|
147
|
+
} else {
|
|
148
|
+
final JSONObject json = new JSONObject(bundle.toJSONMap());
|
|
149
|
+
localEditor.putString(LAST_FAILED_BUNDLE_PREF_KEY, json.toString());
|
|
150
|
+
}
|
|
151
|
+
localEditor.apply();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private BundleInfo readLastFailedBundle() {
|
|
155
|
+
if (this.prefs == null) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
final String raw = this.prefs.getString(LAST_FAILED_BUNDLE_PREF_KEY, null);
|
|
159
|
+
if (raw == null || raw.trim().isEmpty()) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
return BundleInfo.fromJSON(raw);
|
|
164
|
+
} catch (final JSONException e) {
|
|
165
|
+
logger.error("Failed to parse failed bundle info: " + e.getMessage());
|
|
166
|
+
this.persistLastFailedBundle(null);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
126
171
|
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
127
172
|
Thread bgTask = new Thread(() -> {
|
|
128
173
|
try {
|
|
@@ -153,17 +198,23 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
153
198
|
this.implementation = new CapgoUpdater(logger) {
|
|
154
199
|
@Override
|
|
155
200
|
public void notifyDownload(final String id, final int percent) {
|
|
156
|
-
|
|
201
|
+
activity.runOnUiThread(() -> {
|
|
202
|
+
CapacitorUpdaterPlugin.this.notifyDownload(id, percent);
|
|
203
|
+
});
|
|
157
204
|
}
|
|
158
205
|
|
|
159
206
|
@Override
|
|
160
207
|
public void directUpdateFinish(final BundleInfo latest) {
|
|
161
|
-
|
|
208
|
+
activity.runOnUiThread(() -> {
|
|
209
|
+
CapacitorUpdaterPlugin.this.directUpdateFinish(latest);
|
|
210
|
+
});
|
|
162
211
|
}
|
|
163
212
|
|
|
164
213
|
@Override
|
|
165
214
|
public void notifyListeners(final String id, final Map<String, Object> res) {
|
|
166
|
-
|
|
215
|
+
activity.runOnUiThread(() -> {
|
|
216
|
+
CapacitorUpdaterPlugin.this.notifyListeners(id, CapacitorUpdaterPlugin.this.mapToJSObject(res));
|
|
217
|
+
});
|
|
167
218
|
}
|
|
168
219
|
};
|
|
169
220
|
final PackageInfo pInfo = this.getContext().getPackageManager().getPackageInfo(this.getContext().getPackageName(), 0);
|
|
@@ -290,6 +341,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
290
341
|
this.appReadyTimeout = this.getConfig().getInt("appReadyTimeout", 10000);
|
|
291
342
|
this.keepUrlPathAfterReload = this.getConfig().getBoolean("keepUrlPathAfterReload", false);
|
|
292
343
|
this.syncKeepUrlPathFlag(this.keepUrlPathAfterReload);
|
|
344
|
+
this.allowManualBundleError = this.getConfig().getBoolean("allowManualBundleError", false);
|
|
293
345
|
this.autoSplashscreen = this.getConfig().getBoolean("autoSplashscreen", false);
|
|
294
346
|
this.autoSplashscreenLoader = this.getConfig().getBoolean("autoSplashscreenLoader", false);
|
|
295
347
|
int splashscreenTimeoutValue = this.getConfig().getInt("autoSplashscreenTimeout", 10000);
|
|
@@ -586,30 +638,36 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
586
638
|
}
|
|
587
639
|
|
|
588
640
|
private void cleanupObsoleteVersions() {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
641
|
+
startNewThread(() -> {
|
|
642
|
+
try {
|
|
643
|
+
final String previous = this.prefs.getString("LatestNativeBuildVersion", "");
|
|
644
|
+
if (!"".equals(previous) && !Objects.equals(this.currentBuildVersion, previous)) {
|
|
645
|
+
logger.info("New native build version detected: " + this.currentBuildVersion);
|
|
646
|
+
this.implementation.reset(true);
|
|
647
|
+
final List<BundleInfo> installed = this.implementation.list(false);
|
|
648
|
+
for (final BundleInfo bundle : installed) {
|
|
649
|
+
try {
|
|
650
|
+
logger.info("Deleting obsolete bundle: " + bundle.getId());
|
|
651
|
+
this.implementation.delete(bundle.getId());
|
|
652
|
+
} catch (final Exception e) {
|
|
653
|
+
logger.error("Failed to delete: " + bundle.getId() + " " + e.getMessage());
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
final List<BundleInfo> storedBundles = this.implementation.list(true);
|
|
657
|
+
final Set<String> allowedIds = new HashSet<>();
|
|
658
|
+
for (final BundleInfo info : storedBundles) {
|
|
659
|
+
if (info != null && info.getId() != null && !info.getId().isEmpty()) {
|
|
660
|
+
allowedIds.add(info.getId());
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
this.implementation.cleanupDownloadDirectories(allowedIds);
|
|
607
664
|
}
|
|
665
|
+
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
666
|
+
this.editor.apply();
|
|
667
|
+
} catch (Exception e) {
|
|
668
|
+
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
608
669
|
}
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
612
|
-
this.editor.apply();
|
|
670
|
+
});
|
|
613
671
|
}
|
|
614
672
|
|
|
615
673
|
public void notifyDownload(final String id, final int percent) {
|
|
@@ -1041,6 +1099,16 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1041
1099
|
} else {
|
|
1042
1100
|
this.bridge.setServerBasePath(path);
|
|
1043
1101
|
}
|
|
1102
|
+
if (this.bridge != null && this.bridge.getWebView() != null) {
|
|
1103
|
+
this.bridge.getWebView().post(() -> {
|
|
1104
|
+
if (this.bridge.getWebView() != null) {
|
|
1105
|
+
this.bridge.getWebView().loadUrl(this.bridge.getAppUrl());
|
|
1106
|
+
if (!this.keepUrlPathAfterReload) {
|
|
1107
|
+
this.bridge.getWebView().clearHistory();
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1044
1112
|
}
|
|
1045
1113
|
|
|
1046
1114
|
this.checkAppReady();
|
|
@@ -1140,6 +1208,44 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1140
1208
|
}
|
|
1141
1209
|
}
|
|
1142
1210
|
|
|
1211
|
+
@PluginMethod
|
|
1212
|
+
public void setBundleError(final PluginCall call) {
|
|
1213
|
+
if (!Boolean.TRUE.equals(this.allowManualBundleError)) {
|
|
1214
|
+
logger.error("setBundleError called without allowManualBundleError");
|
|
1215
|
+
call.reject("setBundleError not allowed. Set allowManualBundleError to true in your config to enable it.");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
final String id = call.getString("id");
|
|
1219
|
+
if (id == null) {
|
|
1220
|
+
logger.error("setBundleError called without id");
|
|
1221
|
+
call.reject("setBundleError called without id");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
try {
|
|
1225
|
+
final BundleInfo bundle = this.implementation.getBundleInfo(id);
|
|
1226
|
+
if (bundle == null || bundle.isUnknown()) {
|
|
1227
|
+
logger.error("setBundleError called with unknown bundle " + id);
|
|
1228
|
+
call.reject("Bundle " + id + " does not exist");
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (bundle.isBuiltin()) {
|
|
1232
|
+
logger.error("setBundleError called on builtin bundle");
|
|
1233
|
+
call.reject("Cannot set builtin bundle to error state");
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (Boolean.TRUE.equals(this.autoUpdate)) {
|
|
1237
|
+
logger.warn("setBundleError used while autoUpdate is enabled; this method is intended for manual mode");
|
|
1238
|
+
}
|
|
1239
|
+
this.implementation.setError(bundle);
|
|
1240
|
+
final JSObject ret = new JSObject();
|
|
1241
|
+
ret.put("bundle", mapToJSObject(this.implementation.getBundleInfo(id).toJSONMap()));
|
|
1242
|
+
call.resolve(ret);
|
|
1243
|
+
} catch (final Exception e) {
|
|
1244
|
+
logger.error("Could not set bundle error for id " + id + " " + e.getMessage());
|
|
1245
|
+
call.reject("Could not set bundle error for id " + id, e);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1143
1249
|
@PluginMethod
|
|
1144
1250
|
public void list(final PluginCall call) {
|
|
1145
1251
|
try {
|
|
@@ -1236,6 +1342,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1236
1342
|
}
|
|
1237
1343
|
}
|
|
1238
1344
|
|
|
1345
|
+
@PluginMethod
|
|
1346
|
+
public void getFailedUpdate(final PluginCall call) {
|
|
1347
|
+
try {
|
|
1348
|
+
final BundleInfo bundle = this.readLastFailedBundle();
|
|
1349
|
+
if (bundle == null || bundle.isUnknown()) {
|
|
1350
|
+
call.resolve(null);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
this.persistLastFailedBundle(null);
|
|
1355
|
+
|
|
1356
|
+
final JSObject ret = new JSObject();
|
|
1357
|
+
ret.put("bundle", mapToJSObject(bundle.toJSONMap()));
|
|
1358
|
+
call.resolve(ret);
|
|
1359
|
+
} catch (final Exception e) {
|
|
1360
|
+
logger.error("Could not get failed update " + e.getMessage());
|
|
1361
|
+
call.reject("Could not get failed update", e);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1239
1365
|
public void checkForUpdateAfterDelay() {
|
|
1240
1366
|
if (this.periodCheckDelay == 0 || !this._isAutoUpdateEnabled()) {
|
|
1241
1367
|
return;
|
|
@@ -1480,10 +1606,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1480
1606
|
try {
|
|
1481
1607
|
if (jsRes.has("message")) {
|
|
1482
1608
|
logger.info("API message: " + jsRes.get("message"));
|
|
1483
|
-
if (jsRes.has("
|
|
1484
|
-
|
|
1485
|
-
majorAvailable.put("version", jsRes.getString("version"));
|
|
1486
|
-
CapacitorUpdaterPlugin.this.notifyListeners("majorAvailable", majorAvailable);
|
|
1609
|
+
if (jsRes.has("version") && (jsRes.has("breaking") || jsRes.has("major"))) {
|
|
1610
|
+
CapacitorUpdaterPlugin.this.notifyBreakingEvents(jsRes.getString("version"));
|
|
1487
1611
|
}
|
|
1488
1612
|
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
|
|
1489
1613
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
@@ -1738,6 +1862,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1738
1862
|
logger.info("Did you forget to call 'notifyAppReady()' in your Capacitor App code?");
|
|
1739
1863
|
final JSObject ret = new JSObject();
|
|
1740
1864
|
ret.put("bundle", mapToJSObject(current.toJSONMap()));
|
|
1865
|
+
this.persistLastFailedBundle(current);
|
|
1741
1866
|
this.notifyListeners("updateFailed", ret);
|
|
1742
1867
|
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
1743
1868
|
this.implementation.setError(current);
|
|
@@ -35,6 +35,8 @@ import java.util.Objects;
|
|
|
35
35
|
import java.util.Set;
|
|
36
36
|
import java.util.concurrent.CompletableFuture;
|
|
37
37
|
import java.util.concurrent.ConcurrentHashMap;
|
|
38
|
+
import java.util.concurrent.ExecutorService;
|
|
39
|
+
import java.util.concurrent.Executors;
|
|
38
40
|
import java.util.concurrent.TimeUnit;
|
|
39
41
|
import java.util.zip.ZipEntry;
|
|
40
42
|
import java.util.zip.ZipInputStream;
|
|
@@ -80,6 +82,7 @@ public class CapgoUpdater {
|
|
|
80
82
|
public int timeout = 20000;
|
|
81
83
|
|
|
82
84
|
private final Map<String, CompletableFuture<BundleInfo>> downloadFutures = new ConcurrentHashMap<>();
|
|
85
|
+
private final ExecutorService io = Executors.newSingleThreadExecutor();
|
|
83
86
|
|
|
84
87
|
public CapgoUpdater(Logger logger) {
|
|
85
88
|
this.logger = logger;
|
|
@@ -245,60 +248,71 @@ public class CapgoUpdater {
|
|
|
245
248
|
String checksum = outputData.getString(DownloadService.CHECKSUM);
|
|
246
249
|
boolean isManifest = outputData.getBoolean(DownloadService.IS_MANIFEST, false);
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
251
|
+
io.execute(() -> {
|
|
252
|
+
boolean success = finishDownload(id, dest, version, sessionKey, checksum, true, isManifest);
|
|
253
|
+
BundleInfo resultBundle;
|
|
254
|
+
if (!success) {
|
|
255
|
+
logger.error("Finish download failed: " + version);
|
|
256
|
+
resultBundle = new BundleInfo(
|
|
257
|
+
id,
|
|
258
|
+
version,
|
|
259
|
+
BundleStatus.ERROR,
|
|
260
|
+
new Date(System.currentTimeMillis()),
|
|
261
|
+
""
|
|
262
|
+
);
|
|
263
|
+
saveBundleInfo(id, resultBundle);
|
|
264
|
+
// Cleanup download tracking
|
|
265
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
266
|
+
Map<String, Object> ret = new HashMap<>();
|
|
267
|
+
ret.put("version", version);
|
|
268
|
+
ret.put("error", "finish_download_fail");
|
|
269
|
+
sendStats("finish_download_fail", version);
|
|
270
|
+
notifyListeners("downloadFailed", ret);
|
|
271
|
+
} else {
|
|
272
|
+
// Successful download - cleanup tracking
|
|
273
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
274
|
+
resultBundle = getBundleInfo(id);
|
|
275
|
+
}
|
|
266
276
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
// Complete the future if it exists
|
|
278
|
+
CompletableFuture<BundleInfo> future = downloadFutures.remove(id);
|
|
279
|
+
if (future != null) {
|
|
280
|
+
future.complete(resultBundle);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
272
283
|
break;
|
|
273
284
|
case FAILED:
|
|
274
285
|
Data failedData = workInfo.getOutputData();
|
|
275
286
|
String error = failedData.getString(DownloadService.ERROR);
|
|
276
287
|
logger.error("Download failed: " + error + " " + workInfo.getState());
|
|
277
288
|
String failedVersion = failedData.getString(DownloadService.VERSION);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
failedFuture.
|
|
301
|
-
|
|
289
|
+
|
|
290
|
+
io.execute(() -> {
|
|
291
|
+
BundleInfo failedBundle = new BundleInfo(
|
|
292
|
+
id,
|
|
293
|
+
failedVersion,
|
|
294
|
+
BundleStatus.ERROR,
|
|
295
|
+
new Date(System.currentTimeMillis()),
|
|
296
|
+
""
|
|
297
|
+
);
|
|
298
|
+
saveBundleInfo(id, failedBundle);
|
|
299
|
+
// Cleanup download tracking for failed downloads
|
|
300
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, failedVersion);
|
|
301
|
+
Map<String, Object> ret = new HashMap<>();
|
|
302
|
+
ret.put("version", failedVersion);
|
|
303
|
+
if ("low_mem_fail".equals(error)) {
|
|
304
|
+
sendStats("low_mem_fail", failedVersion);
|
|
305
|
+
}
|
|
306
|
+
ret.put("error", error != null ? error : "download_fail");
|
|
307
|
+
sendStats("download_fail", failedVersion);
|
|
308
|
+
notifyListeners("downloadFailed", ret);
|
|
309
|
+
|
|
310
|
+
// Complete the future with error status
|
|
311
|
+
CompletableFuture<BundleInfo> failedFuture = downloadFutures.remove(id);
|
|
312
|
+
if (failedFuture != null) {
|
|
313
|
+
failedFuture.complete(failedBundle);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
302
316
|
break;
|
|
303
317
|
}
|
|
304
318
|
});
|