@capawesome/capacitor-live-update 5.0.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.
Files changed (66) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +509 -0
  4. package/android/build.gradle +62 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +613 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +55 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +437 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +86 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +23 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DeleteBundleOptions.java +18 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +27 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +18 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetChannelOptions.java +18 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetCustomIdOptions.java +18 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +24 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundlesResult.java +27 -0
  18. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetChannelResult.java +21 -0
  19. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetCustomIdResult.java +22 -0
  20. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetDeviceIdResult.java +22 -0
  21. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetVersionCodeResult.java +21 -0
  22. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetVersionNameResult.java +21 -0
  23. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/SyncResult.java +22 -0
  24. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/Callback.java +5 -0
  25. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/EmptyCallback.java +5 -0
  26. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/NonEmptyCallback.java +7 -0
  27. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/Result.java +7 -0
  28. package/android/src/main/res/.gitkeep +0 -0
  29. package/dist/docs.json +785 -0
  30. package/dist/esm/definitions.d.ts +356 -0
  31. package/dist/esm/definitions.js +3 -0
  32. package/dist/esm/definitions.js.map +1 -0
  33. package/dist/esm/index.d.ts +4 -0
  34. package/dist/esm/index.js +7 -0
  35. package/dist/esm/index.js.map +1 -0
  36. package/dist/esm/web.d.ts +21 -0
  37. package/dist/esm/web.js +55 -0
  38. package/dist/esm/web.js.map +1 -0
  39. package/dist/plugin.cjs.js +71 -0
  40. package/dist/plugin.cjs.js.map +1 -0
  41. package/dist/plugin.js +74 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/ios/Plugin/Classes/CustomError.swift +38 -0
  44. package/ios/Plugin/Classes/Options/DeleteBundleOptions.swift +14 -0
  45. package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +20 -0
  46. package/ios/Plugin/Classes/Options/SetBundleOptions.swift +14 -0
  47. package/ios/Plugin/Classes/Options/SetChannelOptions.swift +14 -0
  48. package/ios/Plugin/Classes/Options/SetCustomIdOptions.swift +14 -0
  49. package/ios/Plugin/Classes/Results/GetBundleResult.swift +16 -0
  50. package/ios/Plugin/Classes/Results/GetBundlesResult.swift +16 -0
  51. package/ios/Plugin/Classes/Results/GetChannelResult.swift +16 -0
  52. package/ios/Plugin/Classes/Results/GetCustomIdResult.swift +16 -0
  53. package/ios/Plugin/Classes/Results/GetDeviceIdResult.swift +16 -0
  54. package/ios/Plugin/Classes/Results/GetVersionCodeResult.swift +16 -0
  55. package/ios/Plugin/Classes/Results/GetVersionNameResult.swift +16 -0
  56. package/ios/Plugin/Classes/Results/SyncResult.swift +16 -0
  57. package/ios/Plugin/Info.plist +24 -0
  58. package/ios/Plugin/LiveUpdate.swift +512 -0
  59. package/ios/Plugin/LiveUpdateConfig.swift +7 -0
  60. package/ios/Plugin/LiveUpdatePlugin.h +10 -0
  61. package/ios/Plugin/LiveUpdatePlugin.m +23 -0
  62. package/ios/Plugin/LiveUpdatePlugin.swift +247 -0
  63. package/ios/Plugin/LiveUpdatePreferences.swift +60 -0
  64. package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +4 -0
  65. package/ios/Plugin/Protocols/Result.swift +6 -0
  66. package/package.json +92 -0
@@ -0,0 +1,613 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate;
2
+
3
+ import android.app.Activity;
4
+ import android.content.SharedPreferences;
5
+ import android.content.pm.PackageInfo;
6
+ import android.content.pm.PackageManager;
7
+ import android.os.Build;
8
+ import android.os.Handler;
9
+ import androidx.annotation.NonNull;
10
+ import androidx.annotation.Nullable;
11
+ import com.getcapacitor.Logger;
12
+ import com.getcapacitor.plugin.WebView;
13
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
14
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
15
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
16
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetBundleOptions;
17
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
18
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
19
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundleResult;
20
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundlesResult;
21
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetChannelResult;
22
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetCustomIdResult;
23
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDeviceIdResult;
24
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionCodeResult;
25
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionNameResult;
26
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
27
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
28
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
29
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
30
+ import java.io.File;
31
+ import java.io.IOException;
32
+ import java.util.Arrays;
33
+ import java.util.UUID;
34
+ import net.lingala.zip4j.ZipFile;
35
+ import okhttp3.Call;
36
+ import okhttp3.Callback;
37
+ import okhttp3.HttpUrl;
38
+ import okhttp3.OkHttpClient;
39
+ import okhttp3.Request;
40
+ import okhttp3.Response;
41
+ import okhttp3.ResponseBody;
42
+ import okio.Buffer;
43
+ import okio.BufferedSink;
44
+ import okio.BufferedSource;
45
+ import okio.Okio;
46
+ import org.json.JSONException;
47
+ import org.json.JSONObject;
48
+
49
+ public class LiveUpdate {
50
+
51
+ @NonNull
52
+ private final LiveUpdateConfig config;
53
+
54
+ private final String defaultWebAssetDir;
55
+
56
+ @NonNull
57
+ private final LiveUpdatePlugin plugin;
58
+
59
+ @NonNull
60
+ private final LiveUpdatePreferences preferences;
61
+
62
+ @NonNull
63
+ private final SharedPreferences.Editor webViewSettingsEditor;
64
+
65
+ private final String bundlesDirectory = "_capacitor_live_update_bundles";
66
+ private final Handler rollbackHandler = new Handler();
67
+
68
+ public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
69
+ this.config = config;
70
+ this.defaultWebAssetDir = plugin.getBridge().DEFAULT_WEB_ASSET_DIR;
71
+ this.plugin = plugin;
72
+ this.preferences = new LiveUpdatePreferences(plugin.getContext());
73
+ this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
74
+
75
+ if (config.getEnabled()) {
76
+ if (wasUpdated() && config.getResetOnUpdate()) {
77
+ reset();
78
+ } else {
79
+ startRollbackTimer();
80
+ }
81
+ saveCurrentVersionCode();
82
+ }
83
+ }
84
+
85
+ public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
86
+ String bundleId = options.getBundleId();
87
+
88
+ if (!hasBundle(bundleId)) {
89
+ Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
90
+ callback.error(exception);
91
+ return;
92
+ }
93
+ deleteBundle(bundleId);
94
+
95
+ callback.success();
96
+ }
97
+
98
+ public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback) throws Exception {
99
+ String bundleId = options.getBundleId();
100
+ String url = options.getUrl();
101
+
102
+ // Check if the bundle already exists
103
+ if (hasBundle(bundleId)) {
104
+ Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
105
+ callback.error(exception);
106
+ return;
107
+ }
108
+
109
+ // Download the bundle
110
+ downloadBundle(bundleId, url, callback);
111
+ }
112
+
113
+ public void getBundle(@NonNull NonEmptyCallback callback) {
114
+ String bundleId = getCurrentBundleId();
115
+ if (bundleId.equals(defaultWebAssetDir)) {
116
+ bundleId = null;
117
+ }
118
+ GetBundleResult result = new GetBundleResult(bundleId);
119
+ callback.success(result);
120
+ }
121
+
122
+ public void getBundles(@NonNull NonEmptyCallback callback) {
123
+ String[] bundleIds = getBundleIds();
124
+ GetBundlesResult result = new GetBundlesResult(bundleIds);
125
+ callback.success(result);
126
+ }
127
+
128
+ public void getChannel(@NonNull NonEmptyCallback callback) {
129
+ String channel = preferences.getChannel();
130
+ GetChannelResult result = new GetChannelResult(channel);
131
+ callback.success(result);
132
+ }
133
+
134
+ public void getCustomId(@NonNull NonEmptyCallback callback) {
135
+ String customId = preferences.getCustomId();
136
+ GetCustomIdResult result = new GetCustomIdResult(customId);
137
+ callback.success(result);
138
+ }
139
+
140
+ public void getDeviceId(@NonNull NonEmptyCallback callback) {
141
+ String deviceId = getDeviceId();
142
+ GetDeviceIdResult result = new GetDeviceIdResult(deviceId);
143
+ callback.success(result);
144
+ }
145
+
146
+ public void getVersionCode(@NonNull NonEmptyCallback callback) throws PackageManager.NameNotFoundException {
147
+ String versionCode = getVersionCode();
148
+ GetVersionCodeResult result = new GetVersionCodeResult(versionCode);
149
+ callback.success(result);
150
+ }
151
+
152
+ public void getVersionName(@NonNull NonEmptyCallback callback) throws PackageManager.NameNotFoundException {
153
+ String versionName = getVersionName();
154
+ GetVersionNameResult result = new GetVersionNameResult(versionName);
155
+ callback.success(result);
156
+ }
157
+
158
+ public void ready() {
159
+ Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
160
+ stopRollbackTimer();
161
+ if (config.getAutoDeleteBundles()) {
162
+ deleteUnusedBundles();
163
+ }
164
+ }
165
+
166
+ public void reload() {
167
+ String currentPath = getCurrentCapacitorServerPath();
168
+ String nextPath = getNextCapacitorServerPath();
169
+ String path = nextPath == null ? currentPath : nextPath;
170
+ setCurrentCapacitorServerPath(path);
171
+ startRollbackTimer();
172
+ }
173
+
174
+ public void reset() {
175
+ setNextCapacitorServerPathToDefaultWebAssetDir();
176
+ }
177
+
178
+ public void setBundle(@NonNull SetBundleOptions options, @NonNull EmptyCallback callback) {
179
+ String bundleId = options.getBundleId();
180
+
181
+ if (!hasBundle(bundleId)) {
182
+ Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
183
+ callback.error(exception);
184
+ return;
185
+ }
186
+
187
+ setNextBundle(bundleId);
188
+ }
189
+
190
+ public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
191
+ String channel = options.getChannel();
192
+
193
+ preferences.setChannel(channel);
194
+ callback.success();
195
+ }
196
+
197
+ public void setCustomId(@NonNull SetCustomIdOptions options, @NonNull EmptyCallback callback) {
198
+ String customId = options.getCustomId();
199
+
200
+ preferences.setCustomId(customId);
201
+ callback.success();
202
+ }
203
+
204
+ public void sync(@NonNull NonEmptyCallback<Result> callback) throws Exception {
205
+ // Fetch the latest bundle
206
+ fetchLatestBundle(
207
+ new NonEmptyCallback<GetLatestBundleResponse>() {
208
+ @Override
209
+ public void error(Exception exception) {
210
+ // No update available
211
+ Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
212
+ SyncResult syncResult = new SyncResult(null);
213
+ callback.success(syncResult);
214
+ }
215
+
216
+ @Override
217
+ public void success(@NonNull GetLatestBundleResponse result) {
218
+ String latestBundleId = result.getBundleId();
219
+ String url = result.getUrl();
220
+
221
+ // Check if the bundle already exists
222
+ if (hasBundle(latestBundleId)) {
223
+ String nextBundleId = null;
224
+ String currentBundleId = getCurrentBundleId();
225
+ if (!latestBundleId.equals(currentBundleId)) {
226
+ // Set the next bundle
227
+ setNextBundle(latestBundleId);
228
+ nextBundleId = latestBundleId;
229
+ }
230
+ SyncResult syncResult = new SyncResult(nextBundleId);
231
+ callback.success(syncResult);
232
+ return;
233
+ }
234
+ // Download and unzip the bundle
235
+ downloadBundle(
236
+ latestBundleId,
237
+ url,
238
+ new EmptyCallback() {
239
+ @Override
240
+ public void success() {
241
+ // Set the next bundle
242
+ setNextBundle(latestBundleId);
243
+ SyncResult syncResult = new SyncResult(latestBundleId);
244
+ callback.success(syncResult);
245
+ }
246
+
247
+ @Override
248
+ public void error(Exception exception) {
249
+ callback.error(exception);
250
+ }
251
+ }
252
+ );
253
+ }
254
+ }
255
+ );
256
+ }
257
+
258
+ private void addBundle(@NonNull String bundleId, @NonNull File zipFile) throws Exception {
259
+ // Unzip the file to the bundle directory
260
+ File unzippedDirectory = unzipFile(zipFile);
261
+
262
+ // Search folder with index.html file
263
+ File indexHtmlFile = searchIndexHtmlFile(unzippedDirectory);
264
+ if (indexHtmlFile == null) {
265
+ throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_INDEX_HTML_MISSING);
266
+ }
267
+
268
+ // Move the bundle directory to the bundles directory
269
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
270
+ indexHtmlFile.getParentFile().renameTo(bundleDirectory);
271
+ }
272
+
273
+ private File buildTemporaryDirectory() {
274
+ String fileName = UUID.randomUUID().toString();
275
+ return new File(plugin.getContext().getCacheDir(), fileName);
276
+ }
277
+
278
+ private File buildTemporaryZipFile() {
279
+ String fileName = UUID.randomUUID().toString() + ".zip";
280
+ return new File(plugin.getContext().getCacheDir(), fileName);
281
+ }
282
+
283
+ private File buildBundlesDirectory() {
284
+ return new File(plugin.getContext().getFilesDir(), bundlesDirectory);
285
+ }
286
+
287
+ private File buildBundleDirectoryFor(@NonNull String bundle) {
288
+ return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundle);
289
+ }
290
+
291
+ private void deleteBundle(@NonNull String bundleId) {
292
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
293
+ deleteFileRecursively(bundleDirectory);
294
+ }
295
+
296
+ private void deleteFileRecursively(@NonNull File file) {
297
+ if (file.isDirectory()) {
298
+ for (File child : file.listFiles()) {
299
+ deleteFileRecursively(child);
300
+ }
301
+ }
302
+ file.delete();
303
+ }
304
+
305
+ private void deleteUnusedBundles() {
306
+ String[] bundleIds = getBundleIds();
307
+ for (String bundleId : bundleIds) {
308
+ if (!isBundleInUse(bundleId)) {
309
+ deleteBundle(bundleId);
310
+ }
311
+ }
312
+ }
313
+
314
+ private void downloadBundle(@NonNull String bundleId, @NonNull String url, @NonNull EmptyCallback callback) {
315
+ downloadFile(
316
+ url,
317
+ new NonEmptyCallback<File>() {
318
+ @Override
319
+ public void error(Exception exception) {
320
+ callback.error(exception);
321
+ }
322
+
323
+ @Override
324
+ public void success(@NonNull File result) {
325
+ try {
326
+ // Add the bundle
327
+ addBundle(bundleId, result);
328
+
329
+ // Delete the temporary file
330
+ result.delete();
331
+
332
+ callback.success();
333
+ } catch (Exception exception) {
334
+ callback.error(exception);
335
+ }
336
+ }
337
+ }
338
+ );
339
+ }
340
+
341
+ private void downloadFile(@NonNull String url, @NonNull NonEmptyCallback<File> callback) {
342
+ OkHttpClient okHttpClient = new OkHttpClient();
343
+ Request request = new Request.Builder().url(url).build();
344
+ okHttpClient
345
+ .newCall(request)
346
+ .enqueue(
347
+ new Callback() {
348
+ @Override
349
+ public void onFailure(@NonNull Call call, @NonNull IOException exception) {
350
+ callback.error(exception);
351
+ }
352
+
353
+ @Override
354
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
355
+ ResponseBody responseBody = response.body();
356
+ if (response.isSuccessful()) {
357
+ File destinationFile = buildTemporaryZipFile();
358
+
359
+ long contentLength = responseBody.contentLength();
360
+ BufferedSource source = responseBody.source();
361
+
362
+ BufferedSink sink = Okio.buffer(Okio.sink(destinationFile));
363
+ Buffer sinkBuffer = sink.getBuffer();
364
+
365
+ long totalBytesRead = 0;
366
+ int bufferSize = 8 * 1024;
367
+ for (long bytesRead; (bytesRead = source.read(sinkBuffer, bufferSize)) != -1;) {
368
+ sink.emit();
369
+ totalBytesRead += bytesRead;
370
+ int progress = (int) ((totalBytesRead * 100) / contentLength);
371
+ }
372
+ sink.flush();
373
+ sink.close();
374
+ source.close();
375
+ callback.success(destinationFile);
376
+ } else {
377
+ Exception exception = new Exception(responseBody.string());
378
+ Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
379
+ callback.error(new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED));
380
+ }
381
+ }
382
+ }
383
+ );
384
+ }
385
+
386
+ @Nullable
387
+ private void fetchLatestBundle(@NonNull NonEmptyCallback<GetLatestBundleResponse> callback)
388
+ throws PackageManager.NameNotFoundException {
389
+ HttpUrl url = new HttpUrl.Builder()
390
+ .scheme("https")
391
+ .host("api.cloud.capawesome.io")
392
+ .addPathSegment("v1")
393
+ .addPathSegment("apps")
394
+ .addPathSegment(config.getAppId())
395
+ .addPathSegment("bundles")
396
+ .addPathSegment("latest")
397
+ .addQueryParameter("appVersionCode", getVersionCode())
398
+ .addQueryParameter("appVersionName", getVersionName())
399
+ .addQueryParameter("bundleId", getCurrentBundleId())
400
+ .addQueryParameter("channelName", preferences.getChannel())
401
+ .addQueryParameter("customId", preferences.getCustomId())
402
+ .addQueryParameter("deviceId", getDeviceId())
403
+ .addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
404
+ .addQueryParameter("platform", "0")
405
+ .addQueryParameter("pluginVersion", LiveUpdatePlugin.VERSION)
406
+ .build();
407
+ OkHttpClient client = new OkHttpClient();
408
+ okhttp3.Request request = new okhttp3.Request.Builder().url(url).build();
409
+ client
410
+ .newCall(request)
411
+ .enqueue(
412
+ new okhttp3.Callback() {
413
+ @Override
414
+ public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException exception) {
415
+ callback.error(exception);
416
+ }
417
+
418
+ @Override
419
+ public void onResponse(@NonNull okhttp3.Call call, @NonNull okhttp3.Response response) throws IOException {
420
+ ResponseBody responseBody = response.body();
421
+ if (response.isSuccessful()) {
422
+ String responseBodyString = responseBody.string();
423
+ JSONObject responseJson = null;
424
+ try {
425
+ responseJson = new JSONObject(responseBodyString);
426
+ } catch (JSONException exception) {
427
+ callback.error(exception);
428
+ }
429
+ GetLatestBundleResponse getLatestBundleResponse = new GetLatestBundleResponse(responseJson);
430
+ callback.success(getLatestBundleResponse);
431
+ } else {
432
+ Exception exception = new Exception(responseBody.string());
433
+ Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
434
+ callback.error(exception);
435
+ }
436
+ }
437
+ }
438
+ );
439
+ }
440
+
441
+ private String getVersionCode() throws PackageManager.NameNotFoundException {
442
+ return String.valueOf(getPackageInfo().versionCode);
443
+ }
444
+
445
+ private String getVersionName() throws PackageManager.NameNotFoundException {
446
+ return getPackageInfo().versionName;
447
+ }
448
+
449
+ private String[] getBundleIds() {
450
+ File bundlesDirectory = buildBundlesDirectory();
451
+ File[] bundles = bundlesDirectory.listFiles();
452
+ if (bundles == null) {
453
+ return new String[0];
454
+ }
455
+
456
+ String[] bundleIds = new String[bundles.length];
457
+ for (int i = 0; i < bundles.length; i++) {
458
+ bundleIds[i] = bundles[i].getName();
459
+ }
460
+
461
+ return bundleIds;
462
+ }
463
+
464
+ private String getCurrentBundleId() {
465
+ String currentPath = getCurrentCapacitorServerPath();
466
+ if (currentPath.equals(defaultWebAssetDir)) {
467
+ return defaultWebAssetDir;
468
+ }
469
+ return new File(currentPath).getName();
470
+ }
471
+
472
+ private String getCurrentCapacitorServerPath() {
473
+ return plugin.getBridge().getServerBasePath();
474
+ }
475
+
476
+ @NonNull
477
+ private String getDeviceId() {
478
+ String deviceId = preferences.getDeviceIdForApp(config.getAppId());
479
+ if (deviceId == null) {
480
+ deviceId = UUID.randomUUID().toString();
481
+ preferences.setDeviceIdForApp(config.getAppId(), deviceId);
482
+ }
483
+ return deviceId;
484
+ }
485
+
486
+ private String getNextBundleId() {
487
+ String nextPath = getNextCapacitorServerPath();
488
+ if (nextPath == null) {
489
+ return null;
490
+ }
491
+ if (nextPath.equals(defaultWebAssetDir)) {
492
+ return defaultWebAssetDir;
493
+ }
494
+ return new File(nextPath).getName();
495
+ }
496
+
497
+ @Nullable
498
+ private String getNextCapacitorServerPath() {
499
+ return plugin
500
+ .getContext()
501
+ .getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
502
+ .getString(WebView.CAP_SERVER_PATH, null);
503
+ }
504
+
505
+ private boolean hasBundle(@NonNull String bundleId) {
506
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
507
+ return bundleDirectory.exists();
508
+ }
509
+
510
+ private boolean isBundleInUse(@NonNull String bundleId) {
511
+ String currentBundleId = getCurrentBundleId();
512
+ String nextBundleId = getNextBundleId();
513
+ return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
514
+ }
515
+
516
+ private void rollback() {
517
+ if (getCurrentBundleId() == defaultWebAssetDir) {
518
+ Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
519
+ return;
520
+ }
521
+ Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
522
+ setNextCapacitorServerPathToDefaultWebAssetDir();
523
+ setCurrentCapacitorServerPathToDefaultWebAssetDir();
524
+ }
525
+
526
+ private void saveCurrentVersionCode() throws PackageManager.NameNotFoundException {
527
+ int currentVersionCode = getPackageInfo().versionCode;
528
+ preferences.setLastVersionCode(currentVersionCode);
529
+ }
530
+
531
+ @Nullable
532
+ private File searchIndexHtmlFile(@NonNull File directory) {
533
+ File[] files = directory.listFiles();
534
+ if (files == null) {
535
+ return null;
536
+ }
537
+ String[] fileNames = new String[files.length];
538
+ for (int i = 0; i < files.length; i++) {
539
+ fileNames[i] = files[i].getName();
540
+ }
541
+ if (Arrays.asList(fileNames).contains("index.html")) {
542
+ return new File(directory, "index.html");
543
+ } else {
544
+ for (File file : files) {
545
+ if (file.isDirectory()) {
546
+ File indexHtmlFile = searchIndexHtmlFile(file);
547
+ if (indexHtmlFile != null) {
548
+ return indexHtmlFile;
549
+ }
550
+ }
551
+ }
552
+ }
553
+ return null;
554
+ }
555
+
556
+ private void setCurrentCapacitorServerPath(@NonNull String path) {
557
+ if (path.equals(defaultWebAssetDir)) {
558
+ this.plugin.getBridge().setServerAssetPath(path);
559
+ } else {
560
+ this.plugin.getBridge().setServerBasePath(path);
561
+ }
562
+ this.plugin.getBridge().reload();
563
+ }
564
+
565
+ private void setCurrentCapacitorServerPathToDefaultWebAssetDir() {
566
+ setCurrentCapacitorServerPath(defaultWebAssetDir);
567
+ }
568
+
569
+ private void setNextBundle(@NonNull String bundleId) {
570
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
571
+ setNextCapacitorServerPath(bundleDirectory.getPath());
572
+ }
573
+
574
+ private void setNextCapacitorServerPath(@NonNull String path) {
575
+ this.webViewSettingsEditor.putString(WebView.CAP_SERVER_PATH, path);
576
+ this.webViewSettingsEditor.commit();
577
+ }
578
+
579
+ private void setNextCapacitorServerPathToDefaultWebAssetDir() {
580
+ setNextCapacitorServerPath(defaultWebAssetDir);
581
+ }
582
+
583
+ private void startRollbackTimer() {
584
+ stopRollbackTimer();
585
+ rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
586
+ }
587
+
588
+ private void stopRollbackTimer() {
589
+ rollbackHandler.removeCallbacksAndMessages(null);
590
+ }
591
+
592
+ private File unzipFile(@NonNull File zipFile) throws IOException {
593
+ File destination = buildTemporaryDirectory();
594
+ String destinationPath = destination.getPath();
595
+ new ZipFile(zipFile).extractAll(destinationPath);
596
+ return destination;
597
+ }
598
+
599
+ private boolean wasUpdated() throws PackageManager.NameNotFoundException {
600
+ int lastVersionCode = preferences.getLastVersionCode();
601
+ int currentVersionCode = getPackageInfo().versionCode;
602
+ return lastVersionCode != currentVersionCode;
603
+ }
604
+
605
+ private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
606
+ String packageName = this.plugin.getContext().getPackageName();
607
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
608
+ return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
609
+ } else {
610
+ return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, 0);
611
+ }
612
+ }
613
+ }
@@ -0,0 +1,55 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate;
2
+
3
+ import androidx.annotation.Nullable;
4
+
5
+ public class LiveUpdateConfig {
6
+
7
+ @Nullable
8
+ private String appId = null;
9
+
10
+ private boolean autoDeleteBundles = false;
11
+ private boolean enabled = true;
12
+ private int readyTimeout = 10000;
13
+ private boolean resetOnUpdate = true;
14
+
15
+ @Nullable
16
+ public String getAppId() {
17
+ return appId;
18
+ }
19
+
20
+ public boolean getAutoDeleteBundles() {
21
+ return autoDeleteBundles;
22
+ }
23
+
24
+ public boolean getEnabled() {
25
+ return enabled;
26
+ }
27
+
28
+ public int getReadyTimeout() {
29
+ return readyTimeout;
30
+ }
31
+
32
+ public boolean getResetOnUpdate() {
33
+ return resetOnUpdate;
34
+ }
35
+
36
+ public void setAppId(@Nullable String appId) {
37
+ this.appId = appId;
38
+ }
39
+
40
+ public void setAutoDeleteBundles(boolean autoDeleteBundles) {
41
+ this.autoDeleteBundles = autoDeleteBundles;
42
+ }
43
+
44
+ public void setEnabled(boolean enabled) {
45
+ this.enabled = enabled;
46
+ }
47
+
48
+ public void setReadyTimeout(int readyTimeout) {
49
+ this.readyTimeout = readyTimeout;
50
+ }
51
+
52
+ public void setResetOnUpdate(boolean resetOnUpdate) {
53
+ this.resetOnUpdate = resetOnUpdate;
54
+ }
55
+ }