@capgo/capacitor-updater 3.3.12 → 4.0.0-alpha.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.
@@ -5,12 +5,15 @@ import android.app.Application;
5
5
  import android.content.SharedPreferences;
6
6
  import android.content.pm.PackageInfo;
7
7
  import android.content.pm.PackageManager;
8
+ import android.os.Build;
8
9
  import android.os.Bundle;
10
+ import android.provider.Settings;
9
11
  import android.util.Log;
10
12
 
11
13
  import androidx.annotation.NonNull;
12
14
  import androidx.annotation.Nullable;
13
15
 
16
+ import com.android.volley.toolbox.Volley;
14
17
  import com.getcapacitor.CapConfig;
15
18
  import com.getcapacitor.JSArray;
16
19
  import com.getcapacitor.JSObject;
@@ -18,389 +21,596 @@ import com.getcapacitor.Plugin;
18
21
  import com.getcapacitor.PluginCall;
19
22
  import com.getcapacitor.PluginMethod;
20
23
  import com.getcapacitor.annotation.CapacitorPlugin;
24
+ import com.getcapacitor.plugin.WebView;
25
+
21
26
  import io.github.g00fy2.versioncompare.Version;
22
27
 
23
28
  import org.json.JSONException;
24
29
 
25
30
  import java.io.IOException;
26
- import java.util.ArrayList;
31
+ import java.util.List;
27
32
 
28
33
  @CapacitorPlugin(name = "CapacitorUpdater")
29
34
  public class CapacitorUpdaterPlugin extends Plugin implements Application.ActivityLifecycleCallbacks {
30
- private static final String autoUpdateUrlDefault = "https://capgo.app/api/auto_update";
31
- private static final String statsUrlDefault = "https://capgo.app/api/stats";
32
- private final String TAG = "Capacitor-updater";
33
- private CapacitorUpdater implementation;
35
+ private static final String autoUpdateUrlDefault = "https://xvwzpoazmxkqosrdewyv.functions.supabase.co/updates";
36
+ private static final String statsUrlDefault = "https://xvwzpoazmxkqosrdewyv.functions.supabase.co/stats";
37
+ private static final String DELAY_UPDATE = "delayUpdate";
34
38
 
35
- private SharedPreferences prefs;
36
39
  private SharedPreferences.Editor editor;
40
+ private SharedPreferences prefs;
41
+ private CapacitorUpdater implementation;
37
42
 
43
+ private Integer appReadyTimeout = 10000;
44
+ private Boolean autoDeleteFailed = true;
45
+ private Boolean autoDeletePrevious = true;
46
+ private Boolean autoUpdate = false;
38
47
  private String autoUpdateUrl = "";
39
48
  private Version currentVersionNative;
40
- private Boolean autoUpdate = false;
41
49
  private Boolean resetWhenUpdate = true;
42
50
 
51
+ private volatile Thread appReadyCheck;
52
+
43
53
  @Override
44
54
  public void load() {
45
55
  super.load();
46
- this.prefs = this.getContext().getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
56
+ this.prefs = this.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
47
57
  this.editor = this.prefs.edit();
58
+
48
59
  try {
49
- this.implementation = new CapacitorUpdater(this.getContext()) {
60
+ this.implementation = new CapacitorUpdater() {
50
61
  @Override
51
- public void notifyDownload(final int percent) {
52
- CapacitorUpdaterPlugin.this.notifyDownload(percent);
62
+ public void notifyDownload(final String id, final int percent) {
63
+ CapacitorUpdaterPlugin.this.notifyDownload(id, percent);
53
64
  }
54
65
  };
55
66
  final PackageInfo pInfo = this.getContext().getPackageManager().getPackageInfo(this.getContext().getPackageName(), 0);
67
+ this.implementation.versionBuild = pInfo.versionName;
68
+ this.implementation.versionCode = Integer.toString(pInfo.versionCode);
69
+ this.implementation.requestQueue = Volley.newRequestQueue(this.getContext());
56
70
  this.currentVersionNative = new Version(pInfo.versionName);
57
71
  } catch (final PackageManager.NameNotFoundException e) {
58
- Log.e(this.TAG, "Error instantiating implementation", e);
72
+ Log.e(CapacitorUpdater.TAG, "Error instantiating implementation", e);
59
73
  return;
60
- } catch (final Exception ex) {
61
- Log.e(this.TAG, "Error getting current native app version", ex);
74
+ } catch (final Exception e) {
75
+ Log.e(CapacitorUpdater.TAG, "Error getting current native app version", e);
62
76
  return;
63
77
  }
78
+
64
79
  final CapConfig config = CapConfig.loadDefault(this.getActivity());
65
- this.implementation.setAppId(config.getString("appId", ""));
66
- this.implementation.setStatsUrl(this.getConfig().getString("statsUrl", statsUrlDefault));
80
+ this.implementation.appId = config.getString("appId", "");
81
+ this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
82
+ this.implementation.documentsDir = this.getContext().getFilesDir();
83
+ this.implementation.prefs = this.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
84
+ this.implementation.editor = this.prefs.edit();
85
+ this.implementation.versionOs = Build.VERSION.RELEASE;
86
+ this.implementation.deviceID = Settings.Secure.getString(this.getContext().getContentResolver(), Settings.Secure.ANDROID_ID);
87
+
88
+ this.autoDeleteFailed = this.getConfig().getBoolean("autoDeleteFailed", true);
89
+ this.autoDeletePrevious = this.getConfig().getBoolean("autoDeletePrevious", true);
67
90
  this.autoUpdateUrl = this.getConfig().getString("autoUpdateUrl", autoUpdateUrlDefault);
68
91
  this.autoUpdate = this.getConfig().getBoolean("autoUpdate", false);
92
+ this.appReadyTimeout = this.getConfig().getInt("appReadyTimeout", 10000);
69
93
  this.resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
94
+
70
95
  if (this.resetWhenUpdate) {
71
- final Version LatestVersionNative = new Version(this.prefs.getString("LatestVersionNative", ""));
96
+ this.cleanupObsoleteVersions();
97
+ }
98
+ final Application application = (Application) this.getContext().getApplicationContext();
99
+ application.registerActivityLifecycleCallbacks(this);
100
+
101
+ this.onActivityStarted(this.getActivity());
102
+ }
103
+
104
+ private void cleanupObsoleteVersions() {
105
+ try {
106
+ final Version previous = new Version(this.prefs.getString("LatestVersionNative", ""));
72
107
  try {
73
- if (!LatestVersionNative.equals("") && this.currentVersionNative.getMajor() > LatestVersionNative.getMajor()) {
74
- this._reset(false);
75
- this.editor.putString("LatestVersionAutoUpdate", "");
76
- this.editor.putString("LatestVersionNameAutoUpdate", "");
77
- final ArrayList<String> res = this.implementation.list();
78
- for (int i = 0; i < res.size(); i++) {
108
+ if (!"".equals(previous.getOriginalString()) && this.currentVersionNative.getMajor() > previous.getMajor()) {
109
+
110
+ Log.i(CapacitorUpdater.TAG, "New native major version detected: " + this.currentVersionNative);
111
+ this.implementation.reset(true);
112
+ final List<BundleInfo> installed = this.implementation.list();
113
+ for (final BundleInfo bundle: installed) {
79
114
  try {
80
- final String version = res.get(i);
81
- this.implementation.delete(version, "");
82
- Log.i(this.TAG, "Deleted obsolete version: " + version);
83
- } catch (final IOException e) {
84
- Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
115
+ Log.i(CapacitorUpdater.TAG, "Deleting obsolete bundle: " + bundle.getId());
116
+ this.implementation.delete(bundle.getId());
117
+ } catch (final Exception e) {
118
+ Log.e(CapacitorUpdater.TAG, "Failed to delete: " + bundle.getId(), e);
85
119
  }
86
120
  }
87
121
  }
88
- this.editor.putString("LatestVersionNative", this.currentVersionNative.toString());
89
- this.editor.commit();
90
- } catch (final Exception ex) {
91
- Log.e(this.TAG, "Cannot get the current version " + ex.getMessage());
122
+ } catch (final Exception e) {
123
+ Log.e(CapacitorUpdater.TAG, "Could not determine the current version", e);
92
124
  }
125
+ } catch(final Exception e) {
126
+ Log.e(CapacitorUpdater.TAG, "Error calculating previous native version", e);
93
127
  }
94
- if (!this.autoUpdate || this.autoUpdateUrl.equals("")) return;
95
- final Application application = (Application) this.getContext().getApplicationContext();
96
- application.registerActivityLifecycleCallbacks(this);
97
- this.onActivityStarted(this.getActivity());
128
+ this.editor.putString("LatestVersionNative", this.currentVersionNative.toString());
129
+ this.editor.commit();
98
130
  }
99
131
 
100
- public void notifyDownload(final int percent) {
101
- final JSObject ret = new JSObject();
102
- ret.put("percent", percent);
103
- this.notifyListeners("download", ret);
132
+ public void notifyDownload(final String id, final int percent) {
133
+ try {
134
+ final JSObject ret = new JSObject();
135
+ ret.put("percent", percent);
136
+ var bundle = this.implementation.getBundleInfo(id).toJSON();
137
+ ret.put("bundle", bundle);
138
+ this.notifyListeners("download", ret);
139
+ if (percent == 100) {
140
+ this.notifyListeners("downloadComplete", bundle);
141
+ }
142
+ } catch (final Exception e) {
143
+ Log.e(CapacitorUpdater.TAG, "Could not notify listeners", e);
144
+ }
104
145
  }
105
146
 
147
+
106
148
  @PluginMethod
107
149
  public void getId(final PluginCall call) {
108
- final JSObject ret = new JSObject();
109
- ret.put("id", this.implementation.getDeviceID());
110
- call.resolve(ret);
150
+ try {
151
+ final JSObject ret = new JSObject();
152
+ ret.put("id", this.implementation.deviceID);
153
+ call.resolve(ret);
154
+ } catch (final Exception e) {
155
+ Log.e(CapacitorUpdater.TAG, "Could not get device id", e);
156
+ call.reject("Could not get device id", e);
157
+ }
111
158
  }
112
159
 
113
160
  @PluginMethod
114
161
  public void getPluginVersion(final PluginCall call) {
115
- final JSObject ret = new JSObject();
116
- ret.put("version", this.implementation.pluginVersion);
117
- call.resolve(ret);
162
+ try {
163
+ final JSObject ret = new JSObject();
164
+ ret.put("version", CapacitorUpdater.pluginVersion);
165
+ call.resolve(ret);
166
+ } catch (final Exception e) {
167
+ Log.e(CapacitorUpdater.TAG, "Could not get plugin version", e);
168
+ call.reject("Could not get plugin version", e);
169
+ }
118
170
  }
119
171
 
120
172
  @PluginMethod
121
173
  public void download(final PluginCall call) {
122
- new Thread(new Runnable(){
123
- @Override
124
- public void run() {
125
- try {
126
- final String url = call.getString("url");
127
- final String version = CapacitorUpdaterPlugin.this.implementation.download(url);
128
- final JSObject ret = new JSObject();
129
- ret.put("version", version);
130
- call.resolve(ret);
131
- } catch (final IOException e) {
132
- Log.e(CapacitorUpdaterPlugin.this.TAG, "download failed", e);
133
- call.reject("download failed", e);
174
+ final String url = call.getString("url");
175
+ final String version = call.getString("version");
176
+ if (url == null || version == null) {
177
+ call.reject("missing url or version");
178
+ return;
179
+ }
180
+ try {
181
+ Log.i(CapacitorUpdater.TAG, "Downloading " + url);
182
+ new Thread(new Runnable(){
183
+ @Override
184
+ public void run() {
185
+ try {
186
+
187
+ final BundleInfo downloaded = CapacitorUpdaterPlugin.this.implementation.download(url, version);
188
+ call.resolve(downloaded.toJSON());
189
+ } catch (final IOException e) {
190
+ Log.e(CapacitorUpdater.TAG, "download failed", e);
191
+ call.reject("download failed", e);
192
+ }
134
193
  }
135
- }
136
- }).start();
194
+ }).start();
195
+ } catch (final Exception e) {
196
+ Log.e(CapacitorUpdater.TAG, "Failed to download " + url, e);
197
+ call.reject("Failed to download " + url, e);
198
+ }
137
199
  }
138
200
 
139
201
  private boolean _reload() {
140
- final String pathHot = this.implementation.getLastPathHot();
141
- this.bridge.setServerBasePath(pathHot);
202
+ final String path = this.implementation.getCurrentBundlePath();
203
+ Log.i(CapacitorUpdater.TAG, "Reloading: " + path);
204
+ if(this.implementation.isUsingBuiltin()) {
205
+ this.bridge.setServerAssetPath(path);
206
+ } else {
207
+ this.bridge.setServerBasePath(path);
208
+ }
209
+ this.checkAppReady();
142
210
  return true;
143
211
  }
144
212
 
145
213
  @PluginMethod
146
214
  public void reload(final PluginCall call) {
147
- if (this._reload()) {
148
- call.resolve();
149
- } else {
150
- call.reject("reload failed");
215
+ try {
216
+ if (this._reload()) {
217
+ call.resolve();
218
+ } else {
219
+ call.reject("Reload failed");
220
+ }
221
+ } catch(final Exception e) {
222
+ Log.e(CapacitorUpdater.TAG, "Could not reload", e);
223
+ call.reject("Could not reload", e);
224
+ }
225
+ }
226
+
227
+ @PluginMethod
228
+ public void next(final PluginCall call) {
229
+ final String id = call.getString("id");
230
+
231
+ try {
232
+ Log.i(CapacitorUpdater.TAG, "Setting next active id " + id);
233
+ if (!this.implementation.setNextVersion(id)) {
234
+ call.reject("Set next id failed. Bundle " + id + " does not exist.");
235
+ } else {
236
+ call.resolve(this.implementation.getBundleInfo(id).toJSON());
237
+ }
238
+ } catch (final Exception e) {
239
+ Log.e(CapacitorUpdater.TAG, "Could not set next id " + id, e);
240
+ call.reject("Could not set next id " + id, e);
151
241
  }
152
242
  }
153
243
 
154
244
  @PluginMethod
155
245
  public void set(final PluginCall call) {
156
- final String version = call.getString("version");
157
- final String versionName = call.getString("versionName", version);
158
- final Boolean res = this.implementation.set(version, versionName);
246
+ final String id = call.getString("id");
159
247
 
160
- if (!res) {
161
- call.reject("Update failed, version " + version + " doesn't exist");
162
- } else {
163
- this.reload(call);
248
+ try {
249
+ Log.i(CapacitorUpdater.TAG, "Setting active bundle " + id);
250
+ if (!this.implementation.set(id)) {
251
+ Log.i(CapacitorUpdater.TAG, "No such bundle " + id);
252
+ call.reject("Update failed, id " + id + " does not exist.");
253
+ } else {
254
+ Log.i(CapacitorUpdater.TAG, "Bundle successfully set to" + id);
255
+ this.reload(call);
256
+ }
257
+ } catch(final Exception e) {
258
+ Log.e(CapacitorUpdater.TAG, "Could not set id " + id, e);
259
+ call.reject("Could not set id " + id, e);
164
260
  }
165
261
  }
166
262
 
167
263
  @PluginMethod
168
264
  public void delete(final PluginCall call) {
169
- final String version = call.getString("version");
265
+ final String id = call.getString("id");
266
+ Log.i(CapacitorUpdater.TAG, "Deleting id: " + id);
170
267
  try {
171
- final Boolean res = this.implementation.delete(version, "");
268
+ final Boolean res = this.implementation.delete(id);
172
269
  if (res) {
173
270
  call.resolve();
174
271
  } else {
175
- call.reject("Delete failed, version " + version + " doesn't exist");
272
+ call.reject("Delete failed, id " + id + " does not exist");
176
273
  }
177
- } catch(final IOException ex) {
178
- Log.e(this.TAG, "An unexpected error occurred during deletion of folder. Message: " + ex.getMessage());
179
- call.reject("An unexpected error occurred during deletion of folder.");
274
+ } catch(final Exception e) {
275
+ Log.e(CapacitorUpdater.TAG, "Could not delete id " + id, e);
276
+ call.reject("Could not delete id " + id, e);
180
277
  }
181
278
  }
182
279
 
280
+
183
281
  @PluginMethod
184
282
  public void list(final PluginCall call) {
185
- final ArrayList<String> res = this.implementation.list();
186
- final JSObject ret = new JSObject();
187
- ret.put("versions", new JSArray(res));
188
- call.resolve(ret);
283
+ try {
284
+ final List<BundleInfo> res = this.implementation.list();
285
+ final JSObject ret = new JSObject();
286
+ final JSArray values = new JSArray();
287
+ for (final BundleInfo bundle : res) {
288
+ values.put(bundle.toJSON());
289
+ }
290
+ ret.put("bundles", values);
291
+ call.resolve(ret);
292
+ }
293
+ catch(final Exception e) {
294
+ Log.e(CapacitorUpdater.TAG, "Could not list bundles", e);
295
+ call.reject("Could not list bundles", e);
296
+ }
189
297
  }
190
298
 
191
- private boolean _reset(final Boolean toAutoUpdate) {
192
- final String version = this.prefs.getString("LatestVersionAutoUpdate", "");
193
- final String versionName = this.prefs.getString("LatestVersionNameAutoUpdate", "");
194
- if (toAutoUpdate && !version.equals("") && !versionName.equals("")) {
195
- final Boolean res = this.implementation.set(version, versionName);
196
- return res && this._reload();
197
- }
299
+ private boolean _reset(final Boolean toLastSuccessful) {
300
+ final BundleInfo fallback = this.implementation.getFallbackVersion();
198
301
  this.implementation.reset();
199
- final String pathHot = this.implementation.getLastPathHot();
200
- if (this.bridge.getLocalServer() != null) {
201
- // if the server is not ready yet, hot reload is not needed
202
- this.bridge.setServerAssetPath(pathHot);
302
+
303
+ if (toLastSuccessful && !fallback.isBuiltin()) {
304
+ Log.i(CapacitorUpdater.TAG, "Resetting to: " + fallback);
305
+ return this.implementation.set(fallback) && this._reload();
203
306
  }
204
- return true;
307
+
308
+ Log.i(CapacitorUpdater.TAG, "Resetting to native.");
309
+ return this._reload();
205
310
  }
206
311
 
207
312
  @PluginMethod
208
313
  public void reset(final PluginCall call) {
209
- final Boolean toAutoUpdate = call.getBoolean("toAutoUpdate", false);
210
- if (this._reset(toAutoUpdate)) {
211
- call.resolve();
212
- return;
314
+ try {
315
+ final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
316
+ if (this._reset(toLastSuccessful)) {
317
+ call.resolve();
318
+ return;
319
+ }
320
+ call.reject("Reset failed");
321
+ }
322
+ catch(final Exception e) {
323
+ Log.e(CapacitorUpdater.TAG, "Reset failed", e);
324
+ call.reject("Reset failed", e);
213
325
  }
214
- call.reject("✨ Capacitor-updater: Reset failed");
215
- }
216
-
217
- @PluginMethod
218
- public void versionName(final PluginCall call) {
219
- final String name = this.implementation.getVersionName();
220
- final JSObject ret = new JSObject();
221
- ret.put("versionName", name);
222
- call.resolve(ret);
223
326
  }
224
327
 
225
328
  @PluginMethod
226
329
  public void current(final PluginCall call) {
227
- final String pathHot = this.implementation.getLastPathHot();
228
- final JSObject ret = new JSObject();
229
- final String current = pathHot.length() >= 10 ? pathHot.substring(pathHot.length() - 10) : "builtin";
230
- ret.put("current", current);
231
- ret.put("currentNative", this.currentVersionNative);
232
- call.resolve(ret);
330
+ try {
331
+ final JSObject ret = new JSObject();
332
+ final BundleInfo bundle = this.implementation.getCurrentBundle();
333
+ ret.put("bundle", bundle.toJSON());
334
+ ret.put("native", this.currentVersionNative);
335
+ call.resolve(ret);
336
+ }
337
+ catch(final Exception e) {
338
+ Log.e(CapacitorUpdater.TAG, "Could not get current bundle", e);
339
+ call.reject("Could not get current bundle", e);
340
+ }
233
341
  }
234
342
 
235
343
  @PluginMethod
236
344
  public void notifyAppReady(final PluginCall call) {
237
- this.editor.putBoolean("notifyAppReady", true);
238
- this.editor.commit();
239
- call.resolve();
345
+ try {
346
+ Log.i(CapacitorUpdater.TAG, "Current bundle loaded successfully. ['notifyAppReady()' was called]");
347
+ final BundleInfo bundle = this.implementation.getCurrentBundle();
348
+ this.implementation.commit(bundle);
349
+ call.resolve();
350
+ }
351
+ catch(final Exception e) {
352
+ Log.e(CapacitorUpdater.TAG, "Failed to notify app ready state. [Error calling 'notifyAppReady()']", e);
353
+ call.reject("Failed to commit app ready state.", e);
354
+ }
240
355
  }
241
356
 
242
357
  @PluginMethod
243
358
  public void delayUpdate(final PluginCall call) {
244
- this.editor.putBoolean("delayUpdate", true);
245
- this.editor.commit();
246
- call.resolve();
359
+ try {
360
+ Log.i(CapacitorUpdater.TAG, "Delay update.");
361
+ this.editor.putBoolean(DELAY_UPDATE, true);
362
+ this.editor.commit();
363
+ call.resolve();
364
+ }
365
+ catch(final Exception e) {
366
+ Log.e(CapacitorUpdater.TAG, "Failed to delay update", e);
367
+ call.reject("Failed to delay update", e);
368
+ }
247
369
  }
248
370
 
249
371
  @PluginMethod
250
372
  public void cancelDelay(final PluginCall call) {
251
- this.editor.putBoolean("delayUpdate", false);
252
- this.editor.commit();
253
- call.resolve();
373
+ try {
374
+ Log.i(CapacitorUpdater.TAG, "Cancel update delay.");
375
+ this.editor.putBoolean(DELAY_UPDATE, false);
376
+ this.editor.commit();
377
+ call.resolve();
378
+ }
379
+ catch(final Exception e) {
380
+ Log.e(CapacitorUpdater.TAG, "Failed to cancel update delay", e);
381
+ call.reject("Failed to cancel update delay", e);
382
+ }
254
383
  }
255
384
 
256
- @Override
385
+ private Boolean _isAutoUpdateEnabled() {
386
+ return CapacitorUpdaterPlugin.this.autoUpdate && !"".equals(CapacitorUpdaterPlugin.this.autoUpdateUrl);
387
+ }
388
+
389
+ @PluginMethod
390
+ public void isAutoUpdateEnabled(final PluginCall call) {
391
+ try {
392
+ final JSObject ret = new JSObject();
393
+ ret.put("enabled", this._isAutoUpdateEnabled());
394
+ call.resolve(ret);
395
+ } catch (final Exception e) {
396
+ Log.e(CapacitorUpdater.TAG, "Could not get autoUpdate status", e);
397
+ call.reject("Could not get autoUpdate status", e);
398
+ }
399
+ }
400
+
401
+ private void checkAppReady() {
402
+ try {
403
+ if(this.appReadyCheck != null) {
404
+ this.appReadyCheck.interrupt();
405
+ }
406
+ this.appReadyCheck = new Thread(new DeferredNotifyAppReadyCheck());
407
+ this.appReadyCheck.start();
408
+ } catch (final Exception e) {
409
+ Log.e(CapacitorUpdater.TAG, "Failed to start " + DeferredNotifyAppReadyCheck.class.getName(), e);
410
+ }
411
+ }
412
+
413
+ @Override // appMovedToForeground
257
414
  public void onActivityStarted(@NonNull final Activity activity) {
258
- // disableRevert disableBreaking
259
- Log.i(this.TAG, "Check for update in the server");
260
- if (this.autoUpdateUrl.equals("")) return;
261
- new Thread(new Runnable(){
262
- @Override
263
- public void run() {
264
- CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.autoUpdateUrl, (res) -> {
265
- try {
266
- if (res.has("message")) {
267
- Log.i(CapacitorUpdaterPlugin.this.TAG, "Capacitor-updater: " + res.get("message"));
268
- if (res.has("major") && res.getBoolean("major") && res.has("version")) {
269
- final JSObject ret = new JSObject();
270
- ret.put("newVersion", (String) res.get("version"));
271
- CapacitorUpdaterPlugin.this.notifyListeners("majorAvailable", ret);
415
+ if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled()) {
416
+ new Thread(new Runnable(){
417
+ @Override
418
+ public void run() {
419
+
420
+ Log.i(CapacitorUpdater.TAG, "Check for update via: " + CapacitorUpdaterPlugin.this.autoUpdateUrl);
421
+ CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.autoUpdateUrl, (res) -> {
422
+ try {
423
+ if (res.has("message")) {
424
+ Log.i(CapacitorUpdater.TAG, "message: " + res.get("message"));
425
+ if (res.has("major") && res.getBoolean("major") && res.has("version")) {
426
+ final JSObject majorAvailable = new JSObject();
427
+ majorAvailable.put("version", (String) res.get("version"));
428
+ CapacitorUpdaterPlugin.this.notifyListeners("majorAvailable", majorAvailable);
429
+ }
430
+ return;
272
431
  }
273
- return;
274
- }
275
- final String currentVersion = CapacitorUpdaterPlugin.this.implementation.getVersionName();
276
- final String newVersion = (String) res.get("version");
277
- final JSObject ret = new JSObject();
278
- ret.put("newVersion", newVersion);
279
- final String failingVersion = CapacitorUpdaterPlugin.this.prefs.getString("failingVersion", "");
280
- if (!newVersion.equals("") && !newVersion.equals(failingVersion)) {
281
- new Thread(new Runnable(){
282
- @Override
283
- public void run() {
284
- try {
285
- final String url = (String) res.get("url");
286
- final String dl = CapacitorUpdaterPlugin.this.implementation.download(url);
287
- if (dl.equals("")) {
288
- Log.i(CapacitorUpdaterPlugin.this.TAG, "Download version: " + newVersion + " failed");
289
- return;
290
- }
291
- Log.i(CapacitorUpdaterPlugin.this.TAG, "New version: " + newVersion + " found. Current is " + (currentVersion.equals("") ? "builtin" : currentVersion) + ", next backgrounding will trigger update");
292
- CapacitorUpdaterPlugin.this.editor.putString("nextVersion", dl);
293
- CapacitorUpdaterPlugin.this.editor.putString("nextVersionName", (String) res.get("version"));
294
- CapacitorUpdaterPlugin.this.editor.commit();
295
- CapacitorUpdaterPlugin.this.notifyListeners("updateAvailable", ret);
296
- } catch (final Exception e) {
297
- Log.e(CapacitorUpdaterPlugin.this.TAG, "error downloading file", e);
432
+ final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
433
+ final String latestVersionName = (String) res.get("version");
434
+
435
+ if (latestVersionName != null && !"".equals(latestVersionName) && !current.getVersionName().equals(latestVersionName)) {
436
+
437
+ final BundleInfo latest = CapacitorUpdaterPlugin.this.implementation.getBundleInfoByName(latestVersionName);
438
+ if(latest != null) {
439
+ if(latest.isErrorStatus()) {
440
+ Log.e(CapacitorUpdater.TAG, "Latest bundle already exists, and is in error state. Aborting update.");
441
+ return;
442
+ }
443
+ if(latest.isDownloaded()){
444
+ Log.e(CapacitorUpdater.TAG, "Latest bundle already exists and download is NOT required. Update will occur next time app moves to background.");
445
+ CapacitorUpdaterPlugin.this.implementation.setNextVersion(latest.getId());
446
+ return;
298
447
  }
299
448
  }
300
- }).start();
301
- } else {
302
- Log.i(CapacitorUpdaterPlugin.this.TAG, "No need to update, " + currentVersion + " is the latest");
449
+
450
+
451
+ new Thread(new Runnable(){
452
+ @Override
453
+ public void run() {
454
+ try {
455
+ Log.i(CapacitorUpdater.TAG, "New bundle: " + latestVersionName + " found. Current is: " + current.getVersionName() + ". Update will occur next time app moves to background.");
456
+
457
+ final String url = (String) res.get("url");
458
+ final BundleInfo next = CapacitorUpdaterPlugin.this.implementation.download(url, latestVersionName);
459
+
460
+ CapacitorUpdaterPlugin.this.implementation.setNextVersion(next.getId());
461
+ } catch (final Exception e) {
462
+ Log.e(CapacitorUpdater.TAG, "error downloading file", e);
463
+ }
464
+ }
465
+ }).start();
466
+ } else {
467
+ Log.i(CapacitorUpdater.TAG, "No need to update, " + current + " is the latest bundle.");
468
+ }
469
+ } catch (final JSONException e) {
470
+ Log.e(CapacitorUpdater.TAG, "error parsing JSON", e);
303
471
  }
304
- } catch (final JSONException e) {
305
- Log.e(CapacitorUpdaterPlugin.this.TAG, "error parsing JSON", e);
306
- }
307
- });
308
- }
309
- }).start();
472
+ });
473
+ }
474
+ }).start();
475
+ }
476
+
477
+ this.checkAppReady();
310
478
  }
311
479
 
312
- @Override
480
+ @Override // appMovedToBackground
313
481
  public void onActivityStopped(@NonNull final Activity activity) {
314
- final String pathHot = this.implementation.getLastPathHot();
315
- Log.i(this.TAG, "Check for waiting update");
316
- final String nextVersion = this.prefs.getString("nextVersion", "");
317
- final Boolean delayUpdate = this.prefs.getBoolean("delayUpdate", false);
318
- this.editor.putBoolean("delayUpdate", false);
319
- this.editor.commit();
320
- if (delayUpdate) {
321
- Log.i(this.TAG, "Update delayed to next backgrounding");
322
- return;
323
- }
324
- final String nextVersionName = this.prefs.getString("nextVersionName", "");
325
- final String pastVersion = this.prefs.getString("pastVersion", "");
326
- final String pastVersionName = this.prefs.getString("pastVersionName", "");
327
- final Boolean notifyAppReady = this.prefs.getBoolean("notifyAppReady", false);
328
- final String tmpCurVersion = this.implementation.getLastPathHot();
329
- final String curVersion = tmpCurVersion.substring(tmpCurVersion.lastIndexOf('/') +1);
330
- final String curVersionName = this.implementation.getVersionName();
331
- if (!nextVersion.equals("") && !nextVersionName.equals("")) {
332
- final Boolean res = this.implementation.set(nextVersion, nextVersionName);
333
- if (res && this._reload()) {
334
- Log.i(this.TAG, "Auto update to version: " + nextVersionName);
335
- this.editor.putString("LatestVersionAutoUpdate", nextVersion);
336
- this.editor.putString("LatestVersionNameAutoUpdate", nextVersionName);
337
- this.editor.putString("nextVersion", "");
338
- this.editor.putString("nextVersionName", "");
339
- this.editor.putString("pastVersion", curVersion);
340
- this.editor.putString("pastVersionName", curVersionName);
341
- this.editor.putBoolean("notifyAppReady", false);
342
- this.editor.commit();
343
- } else {
344
- Log.i(this.TAG, "Auto update to version: " + nextVersionName + "Failed");
482
+ Log.i(CapacitorUpdater.TAG, "Checking for pending update");
483
+ try {
484
+ final Boolean delayUpdate = this.prefs.getBoolean(DELAY_UPDATE, false);
485
+ this.editor.putBoolean(DELAY_UPDATE, false);
486
+ this.editor.commit();
487
+
488
+ if (delayUpdate) {
489
+ Log.i(CapacitorUpdater.TAG, "Update delayed to next backgrounding");
490
+ return;
345
491
  }
346
- } else if (!notifyAppReady && !pathHot.equals("public")) {
347
- Log.i(this.TAG, "notifyAppReady never trigger");
348
- Log.i(this.TAG, "Version: " + curVersionName + ", is considered broken");
349
- Log.i(this.TAG, "Will downgraded to version: " + (pastVersionName.equals("") ? "builtin" : pastVersionName) + " for next start");
350
- Log.i(this.TAG, "Don't forget to trigger 'notifyAppReady()' in js code to validate a version.");
351
- this.implementation.sendStats("revert", curVersionName);
352
- if (!pastVersion.equals("") && !pastVersionName.equals("")) {
353
- final Boolean res = this.implementation.set(pastVersion, pastVersionName);
354
- if (res && this._reload()) {
355
- Log.i(this.TAG, "Revert to version: " + (pastVersionName.equals("") ? "builtin" : pastVersionName));
356
- this.editor.putString("LatestVersionAutoUpdate", pastVersion);
357
- this.editor.putString("LatestVersionNameAutoUpdate", pastVersionName);
358
- this.editor.putString("pastVersion", "");
359
- this.editor.putString("pastVersionName", "");
360
- this.editor.commit();
492
+
493
+ final BundleInfo fallback = this.implementation.getFallbackVersion();
494
+ final BundleInfo current = this.implementation.getCurrentBundle();
495
+ final BundleInfo next = this.implementation.getNextVersion();
496
+
497
+ final Boolean success = current.getStatus() == BundleStatus.SUCCESS;
498
+
499
+ Log.d(CapacitorUpdater.TAG, "Fallback bundle is: " + fallback);
500
+ Log.d(CapacitorUpdater.TAG, "Current bundle is: " + current);
501
+
502
+ if (next != null && !next.isErrorStatus() && (next.getId() != current.getId())) {
503
+ // There is a next bundle waiting for activation
504
+ Log.d(CapacitorUpdater.TAG, "Next bundle is: " + next.getVersionName());
505
+ if (this.implementation.set(next) && this._reload()) {
506
+ Log.i(CapacitorUpdater.TAG, "Updated to bundle: " + next.getVersionName());
507
+ this.implementation.setNextVersion(null);
361
508
  } else {
362
- Log.i(this.TAG, "Revert to version: " + (pastVersionName.equals("") ? "builtin" : pastVersionName) + "Failed");
509
+ Log.e(CapacitorUpdater.TAG, "Update to bundle: " + next.getVersionName() + " Failed!");
363
510
  }
364
- } else {
365
- if (this._reset(false)) {
366
- this.editor.putString("LatestVersionAutoUpdate", "");
367
- this.editor.putString("LatestVersionNameAutoUpdate", "");
368
- Log.i(this.TAG, "Auto reset done");
511
+ } else if (!success) {
512
+ // There is a no next bundle, and the current bundle has failed
513
+
514
+ if(!current.isBuiltin()) {
515
+ // Don't try to roll back the builtin bundle. Nothing we can do.
516
+
517
+ this.implementation.rollback(current);
518
+
519
+ Log.i(CapacitorUpdater.TAG, "Update failed: 'notifyAppReady()' was never called.");
520
+ Log.i(CapacitorUpdater.TAG, "Bundle: " + current + ", is in error state.");
521
+ Log.i(CapacitorUpdater.TAG, "Will fallback to: " + fallback + " on application restart.");
522
+ Log.i(CapacitorUpdater.TAG, "Did you forget to call 'notifyAppReady()' in your Capacitor App code?");
523
+ final JSObject ret = new JSObject();
524
+ ret.put("bundle", current);
525
+ this.notifyListeners("updateFailed", ret);
526
+ this.implementation.sendStats("revert", current);
527
+ if (!fallback.isBuiltin() && !fallback.equals(current)) {
528
+ final Boolean res = this.implementation.set(fallback);
529
+ if (res && this._reload()) {
530
+ Log.i(CapacitorUpdater.TAG, "Revert to bundle: " + fallback.getVersionName());
531
+ } else {
532
+ Log.e(CapacitorUpdater.TAG, "Revert to bundle: " + fallback.getVersionName() + " Failed!");
533
+ }
534
+ } else {
535
+ if (this._reset(false)) {
536
+ Log.i(CapacitorUpdater.TAG, "Reverted to 'builtin' bundle.");
537
+ }
538
+ }
539
+
540
+ if (this.autoDeleteFailed) {
541
+ Log.i(CapacitorUpdater.TAG, "Deleting failing bundle: " + current.getVersionName());
542
+ try {
543
+ final Boolean res = this.implementation.delete(current.getId());
544
+ if (res) {
545
+ Log.i(CapacitorUpdater.TAG, "Failed bundle deleted: " + current.getVersionName());
546
+ }
547
+ } catch (final IOException e) {
548
+ Log.e(CapacitorUpdater.TAG, "Failed to delete failed bundle: " + current.getVersionName(), e);
549
+ }
550
+ }
551
+ } else {
552
+ // Nothing we can/should do by default if the 'builtin' bundle fails to call 'notifyAppReady()'.
369
553
  }
370
- }
371
- this.editor.putString("failingVersion", curVersionName);
372
- this.editor.commit();
373
- try {
374
- final Boolean res = this.implementation.delete(curVersion, curVersionName);
375
- if (res) {
376
- Log.i(this.TAG, "Deleted failing version: " + curVersionName);
554
+
555
+ } else if (!fallback.isBuiltin()) {
556
+ // There is a no next bundle, and the current bundle has succeeded
557
+ this.implementation.commit(current);
558
+
559
+ if(this.autoDeletePrevious) {
560
+ Log.i(CapacitorUpdater.TAG, "Bundle successfully loaded: " + current);
561
+ try {
562
+ final Boolean res = this.implementation.delete(fallback.getVersionName());
563
+ if (res) {
564
+ Log.i(CapacitorUpdater.TAG, "Deleted previous bundle: " + fallback.getVersionName());
565
+ }
566
+ } catch (final IOException e) {
567
+ Log.e(CapacitorUpdater.TAG, "Failed to delete previous bundle: " + fallback.getVersionName(), e);
568
+ }
377
569
  }
378
- } catch (final IOException e) {
379
- Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
380
570
  }
381
- } else if (!pastVersion.equals("")) {
382
- Log.i(this.TAG, "Validated version: " + curVersionName);
571
+ }
572
+ catch(final Exception e) {
573
+ Log.e(CapacitorUpdater.TAG, "Error during onActivityStopped", e);
574
+ }
575
+ }
576
+
577
+ private class DeferredNotifyAppReadyCheck implements Runnable {
578
+ @Override
579
+ public void run() {
383
580
  try {
384
- final Boolean res = this.implementation.delete(pastVersion, pastVersionName);
385
- if (res) {
386
- Log.i(this.TAG, "Deleted past version: " + pastVersionName);
581
+ Log.i(CapacitorUpdater.TAG, "Wait for " + CapacitorUpdaterPlugin.this.appReadyTimeout + "ms, then check for notifyAppReady");
582
+ Thread.sleep(CapacitorUpdaterPlugin.this.appReadyTimeout);
583
+ // Automatically roll back to fallback version if notifyAppReady has not been called yet
584
+ final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
585
+ if(current.isBuiltin()) {
586
+ Log.i(CapacitorUpdater.TAG, "Built-in bundle is active. Nothing to do.");
587
+ return;
387
588
  }
388
- } catch (final IOException e) {
389
- Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
589
+
590
+ if(BundleStatus.SUCCESS != current.getStatus()) {
591
+ Log.e(CapacitorUpdater.TAG, "notifyAppReady was not called, roll back current bundle: " + current.getId());
592
+ CapacitorUpdaterPlugin.this.implementation.rollback(current);
593
+ CapacitorUpdaterPlugin.this._reset(true);
594
+ } else {
595
+ Log.i(CapacitorUpdater.TAG, "notifyAppReady was called. This is fine: " + current.getId());
596
+ }
597
+
598
+ CapacitorUpdaterPlugin.this.appReadyCheck = null;
599
+ } catch (final InterruptedException e) {
600
+ Log.e(CapacitorUpdater.TAG, DeferredNotifyAppReadyCheck.class.getName() + " was interrupted.");
390
601
  }
391
- this.editor.putString("pastVersion", "");
392
- this.editor.putString("pastVersionName", "");
393
- this.editor.commit();
394
602
  }
395
603
  }
396
604
 
397
605
  // not use but necessary here to remove warnings
398
606
  @Override
399
607
  public void onActivityResumed(@NonNull final Activity activity) {
608
+ // TODO: Implement background updating based on `backgroundUpdate` and `backgroundUpdateDelay` capacitor.config.ts settings
400
609
  }
401
610
 
402
611
  @Override
403
612
  public void onActivityPaused(@NonNull final Activity activity) {
613
+ // TODO: Implement background updating based on `backgroundUpdate` and `backgroundUpdateDelay` capacitor.config.ts settings
404
614
  }
405
615
  @Override
406
616
  public void onActivityCreated(@NonNull final Activity activity, @Nullable final Bundle savedInstanceState) {