@capawesome/capacitor-live-update 8.0.0 → 8.1.0-dev.c088829.1767373807

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.
@@ -11,10 +11,10 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
13
  s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
14
- s.ios.deployment_target = '16.0'
14
+ s.ios.deployment_target = '15.0'
15
15
  s.dependency 'Capacitor'
16
16
  s.dependency 'Alamofire', '~> 5.10.2'
17
- s.dependency 'SSZipArchive', '~> 2.6.0'
17
+ s.dependency 'ZIPFoundation', '~> 0.9.0'
18
18
  s.swift_version = '5.1'
19
19
  s.static_framework = true
20
20
  end
package/Package.swift CHANGED
@@ -3,7 +3,7 @@ import PackageDescription
3
3
 
4
4
  let package = Package(
5
5
  name: "CapawesomeCapacitorLiveUpdate",
6
- platforms: [.iOS(.v16)],
6
+ platforms: [.iOS(.v15)],
7
7
  products: [
8
8
  .library(
9
9
  name: "CapawesomeCapacitorLiveUpdate",
@@ -12,7 +12,7 @@ let package = Package(
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
14
  .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMinor(from: "5.10.2")),
15
- .package(url: "https://github.com/ZipArchive/ZipArchive.git", .upToNextMinor(from: "2.6.0"))
15
+ .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMinor(from: "0.9.0"))
16
16
  ],
17
17
  targets: [
18
18
  .target(
@@ -21,7 +21,7 @@ let package = Package(
21
21
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
22
22
  .product(name: "Cordova", package: "capacitor-swift-pm"),
23
23
  .product(name: "Alamofire", package: "Alamofire"),
24
- .product(name: "ZipArchive", package: "ZipArchive")
24
+ .product(name: "ZIPFoundation", package: "ZIPFoundation")
25
25
  ],
26
26
  path: "ios/Plugin"),
27
27
  .testTarget(
package/README.md CHANGED
@@ -93,21 +93,6 @@ Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Priva
93
93
 
94
94
  We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278401) as the reason for accessing the [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) API.
95
95
 
96
- #### Minimum Deployment Target
97
-
98
- If you are using **Swift Package Manager**, make sure that your iOS deployment target is set to at least `16.0` in your Xcode project settings (usually in `ios/App/App.xcodeproj`):
99
-
100
- ```diff
101
- -IPHONEOS_DEPLOYMENT_TARGET = 15.0
102
- +IPHONEOS_DEPLOYMENT_TARGET = 16.0
103
- ```
104
-
105
- If you are using **CocoaPods**, make sure that your iOS deployment target is set to at least `16.0` in your `Podfile`:
106
-
107
- ```ruby
108
- platform :ios, '16.0'
109
- ```
110
-
111
96
  ## Configuration
112
97
 
113
98
  <docgen-config>
@@ -1,6 +1,7 @@
1
1
  package io.capawesome.capacitorjs.plugins.liveupdate;
2
2
 
3
3
  import android.app.Activity;
4
+ import android.content.Context;
4
5
  import android.content.SharedPreferences;
5
6
  import android.content.pm.PackageInfo;
6
7
  import android.content.pm.PackageManager;
@@ -13,6 +14,7 @@ import androidx.annotation.Nullable;
13
14
  import com.getcapacitor.Bridge;
14
15
  import com.getcapacitor.Logger;
15
16
  import com.getcapacitor.plugin.WebView;
17
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.CustomExceptions;
16
18
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
17
19
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
18
20
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
@@ -44,6 +46,7 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
44
46
  import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
45
47
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.DownloadProgressCallback;
46
48
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
49
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.LiveUpdateEventEmitter;
47
50
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
48
51
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
49
52
  import java.io.File;
@@ -77,18 +80,27 @@ import org.json.JSONObject;
77
80
 
78
81
  public class LiveUpdate {
79
82
 
83
+ public static final String TAG = "LiveUpdate";
84
+ public static final String VERSION = "8.1.1";
85
+
80
86
  private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
81
87
 
88
+ @NonNull
89
+ private final Bridge bridge;
90
+
82
91
  @NonNull
83
92
  private final LiveUpdateConfig config;
84
93
 
94
+ @NonNull
95
+ private final Context context;
96
+
85
97
  private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
86
98
 
87
99
  @NonNull
88
- private final LiveUpdateHttpClient httpClient;
100
+ private final LiveUpdateEventEmitter eventEmitter;
89
101
 
90
102
  @NonNull
91
- private final LiveUpdatePlugin plugin;
103
+ private final LiveUpdateHttpClient httpClient;
92
104
 
93
105
  @NonNull
94
106
  private final LiveUpdatePreferences preferences;
@@ -105,12 +117,19 @@ public class LiveUpdate {
105
117
  private boolean rollbackPerformed = false;
106
118
  private boolean syncInProgress = false;
107
119
 
108
- public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
120
+ public LiveUpdate(
121
+ @NonNull Context context,
122
+ @NonNull Bridge bridge,
123
+ @NonNull LiveUpdateConfig config,
124
+ @NonNull LiveUpdateEventEmitter eventEmitter
125
+ ) throws PackageManager.NameNotFoundException {
126
+ this.context = context;
127
+ this.bridge = bridge;
109
128
  this.config = config;
129
+ this.eventEmitter = eventEmitter;
110
130
  this.httpClient = new LiveUpdateHttpClient(config);
111
- this.plugin = plugin;
112
- this.preferences = new LiveUpdatePreferences(plugin.getContext());
113
- this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
131
+ this.preferences = new LiveUpdatePreferences(context);
132
+ this.webViewSettingsEditor = context.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
114
133
 
115
134
  // Check version and reset config if version changed
116
135
  checkAndResetConfigIfVersionChanged();
@@ -128,7 +147,7 @@ public class LiveUpdate {
128
147
  String bundleId = options.getBundleId();
129
148
 
130
149
  if (!hasBundleById(bundleId)) {
131
- Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
150
+ Exception exception = CustomExceptions.BUNDLE_NOT_FOUND;
132
151
  callback.error(exception);
133
152
  return;
134
153
  }
@@ -284,9 +303,9 @@ public class LiveUpdate {
284
303
  }
285
304
 
286
305
  public void ready(@NonNull NonEmptyCallback callback) {
287
- Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
306
+ Logger.debug(LiveUpdate.TAG, "App is ready.");
288
307
  if (config.getReadyTimeout() <= 0) {
289
- Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
308
+ Logger.warn(LiveUpdate.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
290
309
  }
291
310
  // Stop the rollback timer
292
311
  stopRollbackTimer();
@@ -352,7 +371,7 @@ public class LiveUpdate {
352
371
  if (hasBundleById(bundleId)) {
353
372
  setNextBundleById(bundleId);
354
373
  } else {
355
- Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
374
+ Exception exception = CustomExceptions.BUNDLE_NOT_FOUND;
356
375
  callback.error(exception);
357
376
  return;
358
377
  }
@@ -362,7 +381,7 @@ public class LiveUpdate {
362
381
 
363
382
  public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback) {
364
383
  if (syncInProgress) {
365
- Exception exception = new Exception(LiveUpdatePlugin.ERROR_SYNC_IN_PROGRESS);
384
+ Exception exception = CustomExceptions.SYNC_IN_PROGRESS;
366
385
  callback.error(exception);
367
386
  return;
368
387
  }
@@ -378,7 +397,7 @@ public class LiveUpdate {
378
397
  public void success(@Nullable GetLatestBundleResponse response) {
379
398
  try {
380
399
  if (response == null) {
381
- Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
400
+ Logger.debug(LiveUpdate.TAG, "No update available.");
382
401
  syncInProgress = false;
383
402
  SyncResult syncResult = new SyncResult(null);
384
403
  callback.success(syncResult);
@@ -391,7 +410,7 @@ public class LiveUpdate {
391
410
  String url = response.getUrl();
392
411
  // Check if the bundle is blocked
393
412
  if (isBlockedBundleId(latestBundleId)) {
394
- Logger.warn(LiveUpdatePlugin.TAG, "Bundle is blocked and will not be downloaded.");
413
+ Logger.warn(LiveUpdate.TAG, "Bundle is blocked and will not be downloaded.");
395
414
  syncInProgress = false;
396
415
  SyncResult syncResult = new SyncResult(null);
397
416
  callback.success(syncResult);
@@ -459,7 +478,7 @@ public class LiveUpdate {
459
478
  // Search folder with index.html file
460
479
  File indexHtmlFile = searchIndexHtmlFile(sourceDirectory);
461
480
  if (indexHtmlFile == null) {
462
- throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_INDEX_HTML_MISSING);
481
+ throw CustomExceptions.BUNDLE_INDEX_HTML_MISSING;
463
482
  }
464
483
 
465
484
  // Create the bundles directory if it does not exist
@@ -482,21 +501,21 @@ public class LiveUpdate {
482
501
  }
483
502
 
484
503
  private File buildBundlesDirectory() {
485
- return new File(plugin.getContext().getFilesDir(), bundlesDirectory);
504
+ return new File(context.getFilesDir(), bundlesDirectory);
486
505
  }
487
506
 
488
507
  private File buildBundleDirectoryFor(@NonNull String bundleId) {
489
- return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
508
+ return new File(context.getFilesDir(), bundlesDirectory + "/" + bundleId);
490
509
  }
491
510
 
492
511
  private File buildTemporaryDirectory() {
493
512
  String fileName = UUID.randomUUID().toString();
494
- return new File(plugin.getContext().getCacheDir(), fileName);
513
+ return new File(context.getCacheDir(), fileName);
495
514
  }
496
515
 
497
516
  private File buildTemporaryZipFile() {
498
517
  String fileName = UUID.randomUUID().toString() + ".zip";
499
- return new File(plugin.getContext().getCacheDir(), fileName);
518
+ return new File(context.getCacheDir(), fileName);
500
519
  }
501
520
 
502
521
  private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
@@ -504,7 +523,7 @@ public class LiveUpdate {
504
523
  String currentBundleId = getCurrentBundleId();
505
524
  if (currentBundleId == null) {
506
525
  // Create the source input stream
507
- AssetManager assets = plugin.getContext().getAssets();
526
+ AssetManager assets = context.getAssets();
508
527
  InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
509
528
  // Create the destination file
510
529
  File destination = new File(destinationDirectory, href);
@@ -532,7 +551,7 @@ public class LiveUpdate {
532
551
  for (ManifestItem fileToCopy : filesToCopy) {
533
552
  boolean success = tryCopyCurrentBundleFile(fileToCopy, destinationDirectory);
534
553
  if (!success) {
535
- Logger.warn(LiveUpdatePlugin.TAG, "Failed to copy file: " + fileToCopy.getHref());
554
+ Logger.warn(LiveUpdate.TAG, "Failed to copy file: " + fileToCopy.getHref());
536
555
  // If the file could not be copied, add it to the list of missing items
537
556
  missingItems.add(fileToCopy);
538
557
  }
@@ -578,8 +597,8 @@ public class LiveUpdate {
578
597
  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
579
598
  return keyFactory.generatePublic(X509publicKey);
580
599
  } catch (Exception exception) {
581
- Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
582
- throw new Exception(LiveUpdatePlugin.ERROR_PUBLIC_KEY_INVALID);
600
+ Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
601
+ throw CustomExceptions.PUBLIC_KEY_INVALID;
583
602
  }
584
603
  }
585
604
 
@@ -645,8 +664,8 @@ public class LiveUpdate {
645
664
  completionCallback.success();
646
665
  } else {
647
666
  String errorMessage = response.body().string();
648
- Exception exception = new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED);
649
- Logger.error(LiveUpdatePlugin.TAG, errorMessage, exception);
667
+ Exception exception = CustomExceptions.DOWNLOAD_FAILED;
668
+ Logger.error(LiveUpdate.TAG, errorMessage, exception);
650
669
  completionCallback.error(exception);
651
670
  }
652
671
  } catch (Exception e) {
@@ -749,7 +768,7 @@ public class LiveUpdate {
749
768
  public void error(@NonNull Exception e) {
750
769
  // Capture first error and cancel all remaining downloads
751
770
  if (firstError.compareAndSet(null, e)) {
752
- Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + item.getHref(), e);
771
+ Logger.error(LiveUpdate.TAG, "Failed to download file: " + item.getHref(), e);
753
772
  // Cancel all in-flight downloads (fail-fast)
754
773
  synchronized (activeCalls) {
755
774
  for (Call activeCall : activeCalls) {
@@ -924,10 +943,10 @@ public class LiveUpdate {
924
943
  .addQueryParameter("deviceId", getDeviceId())
925
944
  .addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
926
945
  .addQueryParameter("platform", "0")
927
- .addQueryParameter("pluginVersion", LiveUpdatePlugin.VERSION)
946
+ .addQueryParameter("pluginVersion", LiveUpdate.VERSION)
928
947
  .build()
929
948
  .toString();
930
- Logger.debug(LiveUpdatePlugin.TAG, "Fetching latest bundle: " + url);
949
+ Logger.debug(LiveUpdate.TAG, "Fetching latest bundle: " + url);
931
950
 
932
951
  httpClient.enqueue(
933
952
  url,
@@ -936,7 +955,7 @@ public class LiveUpdate {
936
955
  public void success(@NonNull Response response) {
937
956
  try {
938
957
  String responseBodyString = response.body().string();
939
- Logger.debug(LiveUpdatePlugin.TAG, "Latest bundle response: " + responseBodyString);
958
+ Logger.debug(LiveUpdate.TAG, "Latest bundle response: " + responseBodyString);
940
959
  if (response.isSuccessful()) {
941
960
  JSONObject responseJson = new JSONObject(responseBodyString);
942
961
  GetLatestBundleResponse result = new GetLatestBundleResponse(responseJson);
@@ -986,8 +1005,8 @@ public class LiveUpdate {
986
1005
  source.close();
987
1006
  return digest.digest();
988
1007
  } catch (IOException exception) {
989
- Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
990
- throw new Exception(LiveUpdatePlugin.ERROR_CHECKSUM_CALCULATION_FAILED);
1008
+ Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
1009
+ throw CustomExceptions.CHECKSUM_CALCULATION_FAILED;
991
1010
  }
992
1011
  }
993
1012
 
@@ -1037,7 +1056,7 @@ public class LiveUpdate {
1037
1056
  * @return The absolute path to the current bundle directory (`public` for the built-in bundle).
1038
1057
  */
1039
1058
  private String getCurrentCapacitorServerPath() {
1040
- return plugin.getBridge().getServerBasePath();
1059
+ return bridge.getServerBasePath();
1041
1060
  }
1042
1061
 
1043
1062
  @NonNull
@@ -1067,8 +1086,7 @@ public class LiveUpdate {
1067
1086
  */
1068
1087
  @NonNull
1069
1088
  private String getNextCapacitorServerPath() {
1070
- String path = plugin
1071
- .getContext()
1089
+ String path = context
1072
1090
  .getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
1073
1091
  .getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
1074
1092
  // Empty path means default path
@@ -1113,7 +1131,7 @@ public class LiveUpdate {
1113
1131
  private Manifest loadCurrentManifest() throws Exception {
1114
1132
  String currentBundleId = getCurrentBundleId();
1115
1133
  if (currentBundleId == null) {
1116
- AssetManager assets = plugin.getContext().getAssets();
1134
+ AssetManager assets = context.getAssets();
1117
1135
  boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
1118
1136
  if (manifestFileExists) {
1119
1137
  InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
@@ -1145,14 +1163,14 @@ public class LiveUpdate {
1145
1163
  }
1146
1164
 
1147
1165
  private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
1148
- plugin.notifyDownloadBundleProgressListeners(event);
1166
+ eventEmitter.onDownloadBundleProgress(event);
1149
1167
  }
1150
1168
 
1151
1169
  private void performAutoUpdate() {
1152
1170
  // Check if enough time has passed since the last check
1153
1171
  long now = System.currentTimeMillis();
1154
1172
  if (lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs) {
1155
- Logger.debug(LiveUpdatePlugin.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
1173
+ Logger.debug(LiveUpdate.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
1156
1174
  return;
1157
1175
  }
1158
1176
 
@@ -1160,17 +1178,17 @@ public class LiveUpdate {
1160
1178
  lastAutoUpdateCheckTimestamp = now;
1161
1179
 
1162
1180
  // Run sync
1163
- Logger.debug(LiveUpdatePlugin.TAG, "Auto-update started.");
1181
+ Logger.debug(LiveUpdate.TAG, "Auto-update started.");
1164
1182
  SyncOptions options = new SyncOptions((String) null);
1165
1183
  NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
1166
1184
  @Override
1167
1185
  public void success(@NonNull Result result) {
1168
- Logger.debug(LiveUpdatePlugin.TAG, "Auto-update completed successfully.");
1186
+ Logger.debug(LiveUpdate.TAG, "Auto-update completed successfully.");
1169
1187
  }
1170
1188
 
1171
1189
  @Override
1172
1190
  public void error(@NonNull Exception exception) {
1173
- Logger.error(LiveUpdatePlugin.TAG, "Auto-update failed: " + exception.getMessage(), exception);
1191
+ Logger.error(LiveUpdate.TAG, "Auto-update failed: " + exception.getMessage(), exception);
1174
1192
  }
1175
1193
  };
1176
1194
  sync(options, callback);
@@ -1184,9 +1202,9 @@ public class LiveUpdate {
1184
1202
  setPreviousBundleId(currentBundleId);
1185
1203
  // Log the rollback result
1186
1204
  if (currentBundleId == null) {
1187
- Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
1205
+ Logger.debug(LiveUpdate.TAG, "App is not ready. Default bundle is already in use.");
1188
1206
  } else {
1189
- Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
1207
+ Logger.debug(LiveUpdate.TAG, "App is not ready. Rolling back to default bundle.");
1190
1208
  // Rollback to the default bundle
1191
1209
  setNextBundleById(null);
1192
1210
  setCurrentBundleById(null);
@@ -1232,11 +1250,11 @@ public class LiveUpdate {
1232
1250
 
1233
1251
  private void setCurrentCapacitorServerPath(@NonNull String path) {
1234
1252
  if (path.equals(defaultWebAssetDir)) {
1235
- this.plugin.getBridge().setServerAssetPath(path);
1253
+ this.bridge.setServerAssetPath(path);
1236
1254
  } else {
1237
- this.plugin.getBridge().setServerBasePath(path);
1255
+ this.bridge.setServerBasePath(path);
1238
1256
  }
1239
- this.plugin.getBridge().reload();
1257
+ this.bridge.reload();
1240
1258
  // Notify listeners
1241
1259
  notifyReloadedListeners();
1242
1260
  }
@@ -1258,11 +1276,11 @@ public class LiveUpdate {
1258
1276
 
1259
1277
  private void notifyNextBundleSetListeners(@Nullable String bundleId) {
1260
1278
  NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
1261
- plugin.notifyNextBundleSetListeners(event);
1279
+ eventEmitter.onNextBundleSet(event);
1262
1280
  }
1263
1281
 
1264
1282
  private void notifyReloadedListeners() {
1265
- plugin.notifyReloadedListeners();
1283
+ eventEmitter.onReloaded();
1266
1284
  }
1267
1285
 
1268
1286
  private void addBlockedBundleId(@NonNull String bundleId) {
@@ -1292,7 +1310,7 @@ public class LiveUpdate {
1292
1310
  String newBlockedIds = String.join(",", blockedList);
1293
1311
  preferences.setBlockedBundleIds(newBlockedIds);
1294
1312
 
1295
- Logger.debug(LiveUpdatePlugin.TAG, "Bundle blocked: " + bundleId);
1313
+ Logger.debug(LiveUpdate.TAG, "Bundle blocked: " + bundleId);
1296
1314
  }
1297
1315
 
1298
1316
  private boolean isBlockedBundleId(@NonNull String bundleId) {
@@ -1316,7 +1334,7 @@ public class LiveUpdate {
1316
1334
 
1317
1335
  if (lastVersionCode == -1 || lastVersionCode != currentVersionCode) {
1318
1336
  Logger.debug(
1319
- LiveUpdatePlugin.TAG,
1337
+ LiveUpdate.TAG,
1320
1338
  "App version changed (last: " + lastVersionCode + ", current: " + currentVersionCode + "), resetting config."
1321
1339
  );
1322
1340
  resetConfig();
@@ -1366,12 +1384,12 @@ public class LiveUpdate {
1366
1384
  if (publicKey != null) {
1367
1385
  // Verify the signature
1368
1386
  if (signature == null) {
1369
- throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_MISSING);
1387
+ throw CustomExceptions.SIGNATURE_MISSING;
1370
1388
  }
1371
1389
  // Verify the signature
1372
1390
  boolean verified = verifySignatureForFile(file, signature, publicKey);
1373
1391
  if (!verified) {
1374
- throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_VERIFICATION_FAILED);
1392
+ throw CustomExceptions.SIGNATURE_VERIFICATION_FAILED;
1375
1393
  }
1376
1394
  }
1377
1395
  // Verify the checksum
@@ -1379,7 +1397,7 @@ public class LiveUpdate {
1379
1397
  // Calculate the checksum
1380
1398
  boolean verified = verifyChecksumForFile(file, checksum);
1381
1399
  if (!verified) {
1382
- throw new Exception(LiveUpdatePlugin.ERROR_CHECKSUM_MISMATCH);
1400
+ throw CustomExceptions.CHECKSUM_MISMATCH;
1383
1401
  }
1384
1402
  }
1385
1403
  }
@@ -1402,17 +1420,17 @@ public class LiveUpdate {
1402
1420
  source.close();
1403
1421
  return sig.verify(signatureBytes);
1404
1422
  } catch (Exception exception) {
1405
- Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
1406
- throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_VERIFICATION_FAILED);
1423
+ Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
1424
+ throw CustomExceptions.SIGNATURE_VERIFICATION_FAILED;
1407
1425
  }
1408
1426
  }
1409
1427
 
1410
1428
  private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
1411
- String packageName = this.plugin.getContext().getPackageName();
1429
+ String packageName = this.context.getPackageName();
1412
1430
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
1413
- return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
1431
+ return this.context.getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
1414
1432
  } else {
1415
- return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, 0);
1433
+ return this.context.getPackageManager().getPackageInfo(packageName, 0);
1416
1434
  }
1417
1435
  }
1418
1436
  }
@@ -28,15 +28,15 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDownloade
28
28
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetNextBundleResult;
29
29
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.IsSyncingResult;
30
30
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
31
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.LiveUpdateEventEmitter;
31
32
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
32
33
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
33
34
 
34
35
  @CapacitorPlugin(name = "LiveUpdate")
35
36
  public class LiveUpdatePlugin extends Plugin {
36
37
 
37
- public static final String TAG = "LiveUpdate";
38
- public static final String VERSION = "7.4.0";
39
- public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
38
+ public static final String TAG = LiveUpdate.TAG;
39
+ public static final String VERSION = LiveUpdate.VERSION;
40
40
  public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
41
41
  public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
42
42
  public static final String ERROR_BUNDLE_ID_MISSING = "bundleId must be provided.";
@@ -68,7 +68,8 @@ public class LiveUpdatePlugin extends Plugin {
68
68
  public void load() {
69
69
  try {
70
70
  config = getLiveUpdateConfig();
71
- implementation = new LiveUpdate(config, this);
71
+ LiveUpdateEventEmitter eventEmitter = new PluginLiveUpdateEventEmitter();
72
+ implementation = new LiveUpdate(getContext(), getBridge(), config, eventEmitter);
72
73
  } catch (Exception exception) {
73
74
  Logger.error(TAG, exception.getMessage(), exception);
74
75
  }
@@ -684,4 +685,22 @@ public class LiveUpdatePlugin extends Plugin {
684
685
  Logger.error(TAG, message, exception);
685
686
  call.reject(message);
686
687
  }
688
+
689
+ private class PluginLiveUpdateEventEmitter implements LiveUpdateEventEmitter {
690
+
691
+ @Override
692
+ public void onDownloadBundleProgress(@NonNull DownloadBundleProgressEvent event) {
693
+ notifyDownloadBundleProgressListeners(event);
694
+ }
695
+
696
+ @Override
697
+ public void onNextBundleSet(@NonNull NextBundleSetEvent event) {
698
+ notifyNextBundleSetListeners(event);
699
+ }
700
+
701
+ @Override
702
+ public void onReloaded() {
703
+ notifyReloadedListeners();
704
+ }
705
+ }
687
706
  }
@@ -9,6 +9,8 @@ import java.util.UUID;
9
9
 
10
10
  public class LiveUpdatePreferences {
11
11
 
12
+ private static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
13
+
12
14
  @NonNull
13
15
  private final Context context;
14
16
 
@@ -25,53 +27,45 @@ public class LiveUpdatePreferences {
25
27
 
26
28
  public LiveUpdatePreferences(@NonNull Context context) {
27
29
  this.context = context;
28
- this.settingsEditor = context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).edit();
30
+ this.settingsEditor = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).edit();
29
31
  }
30
32
 
31
33
  @Nullable
32
34
  public String getAppId() {
33
- return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(appIdKey, null);
35
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(appIdKey, null);
34
36
  }
35
37
 
36
38
  @Nullable
37
39
  public String getChannel() {
38
- return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(channelKey, null);
40
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(channelKey, null);
39
41
  }
40
42
 
41
43
  @Nullable
42
44
  public String getCustomId() {
43
- return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(customIdKey, null);
45
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(customIdKey, null);
44
46
  }
45
47
 
46
48
  @Nullable
47
49
  public String getDeviceIdForApp(@Nullable String appId) {
48
50
  if (appId == null) {
49
- return context
50
- .getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
51
- .getString(deviceIdKey, null);
51
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(deviceIdKey, null);
52
52
  } else {
53
- return context
54
- .getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
55
- .getString(deviceIdKey + "_" + appId, null);
53
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(deviceIdKey + "_" + appId, null);
56
54
  }
57
55
  }
58
56
 
59
57
  public int getLastVersionCode() {
60
- return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getInt(lastVersionCodeKey, -1);
58
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getInt(lastVersionCodeKey, -1);
61
59
  }
62
60
 
63
61
  @Nullable
64
62
  public String getPreviousBundleId() {
65
- return context
66
- .getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
67
- .getString(previousBundleIdKey, null);
63
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(previousBundleIdKey, null);
68
64
  }
69
65
 
70
66
  @Nullable
71
67
  public String getBlockedBundleIds() {
72
- return context
73
- .getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
74
- .getString(blockedBundleIdsKey, null);
68
+ return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(blockedBundleIdsKey, null);
75
69
  }
76
70
 
77
71
  public void setAppId(@Nullable String appId) {
@@ -0,0 +1,20 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ public class CustomException extends Exception {
7
+
8
+ @Nullable
9
+ private final String code;
10
+
11
+ public CustomException(@Nullable String code, @NonNull String message) {
12
+ super(message);
13
+ this.code = code;
14
+ }
15
+
16
+ @Nullable
17
+ public String getCode() {
18
+ return code;
19
+ }
20
+ }
@@ -0,0 +1,33 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes;
2
+
3
+ public class CustomExceptions {
4
+
5
+ public static final CustomException APP_ID_MISSING = new CustomException("APP_ID_MISSING", "appId must be configured.");
6
+ public static final CustomException BUNDLE_EXISTS = new CustomException("BUNDLE_EXISTS", "bundle already exists.");
7
+ public static final CustomException BUNDLE_ID_MISSING = new CustomException(null, "bundleId must be provided.");
8
+ public static final CustomException BUNDLE_INDEX_HTML_MISSING = new CustomException(
9
+ "BUNDLE_INDEX_HTML_MISSING",
10
+ "The bundle does not contain an index.html file."
11
+ );
12
+ public static final CustomException BUNDLE_NOT_FOUND = new CustomException("BUNDLE_NOT_FOUND", "bundle not found.");
13
+ public static final CustomException CHECKSUM_CALCULATION_FAILED = new CustomException(
14
+ "CHECKSUM_CALCULATION_FAILED",
15
+ "Failed to calculate checksum."
16
+ );
17
+ public static final CustomException CHECKSUM_MISMATCH = new CustomException("CHECKSUM_MISMATCH", "Checksum mismatch.");
18
+ public static final CustomException CUSTOM_ID_MISSING = new CustomException(null, "customId must be provided.");
19
+ public static final CustomException DOWNLOAD_FAILED = new CustomException("DOWNLOAD_FAILED", "Bundle could not be downloaded.");
20
+ public static final CustomException HTTP_TIMEOUT = new CustomException("HTTP_TIMEOUT", "Request timed out.");
21
+ public static final CustomException URL_MISSING = new CustomException(null, "url must be provided.");
22
+ public static final CustomException SIGNATURE_VERIFICATION_FAILED = new CustomException(
23
+ "SIGNATURE_VERIFICATION_FAILED",
24
+ "Signature verification failed."
25
+ );
26
+ public static final CustomException PUBLIC_KEY_INVALID = new CustomException("PUBLIC_KEY_INVALID", "Invalid public key.");
27
+ public static final CustomException SIGNATURE_MISSING = new CustomException(
28
+ "SIGNATURE_MISSING",
29
+ "Bundle does not contain a signature."
30
+ );
31
+ public static final CustomException SYNC_IN_PROGRESS = new CustomException("SYNC_IN_PROGRESS", "Sync is already in progress.");
32
+ public static final CustomException UNKNOWN_ERROR = new CustomException("UNKNOWN_ERROR", "An unknown error has occurred.");
33
+ }
@@ -0,0 +1,13 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.interfaces;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
5
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
6
+
7
+ public interface LiveUpdateEventEmitter {
8
+ void onDownloadBundleProgress(@NonNull DownloadBundleProgressEvent event);
9
+
10
+ void onNextBundleSet(@NonNull NextBundleSetEvent event);
11
+
12
+ void onReloaded();
13
+ }
@@ -1,26 +1,26 @@
1
1
  import Foundation
2
2
  import CryptoKit
3
- #if canImport(ZipArchive)
4
- import ZipArchive
5
- #else
6
- import SSZipArchive
7
- #endif
3
+ import ZIPFoundation
8
4
  import Capacitor
9
5
  import Alamofire
10
6
  import CommonCrypto
11
7
 
12
8
  // swiftlint:disable type_body_length
13
9
  @objc public class LiveUpdate: NSObject {
10
+ public static let tag = "LiveUpdate"
11
+ public static let version = "8.1.1"
12
+
14
13
  private let autoUpdateIntervalMs: Int64 = 15 * 60 * 1000 // 15 minutes
14
+ private let bridge: CAPBridgeProtocol
15
15
  private let bundlesDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE! (See https://dub.sh/BLluidt)
16
16
  private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
17
17
  private let config: LiveUpdateConfig
18
18
  private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
19
19
  private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
20
+ private let eventEmitter: LiveUpdateEventEmitter
20
21
  private let httpClient: LiveUpdateHttpClient
21
22
  private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
22
23
  private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
23
- private let plugin: LiveUpdatePlugin
24
24
  private let preferences: LiveUpdatePreferences
25
25
 
26
26
  private var rollbackDispatchWorkItem: DispatchWorkItem?
@@ -28,10 +28,11 @@ import CommonCrypto
28
28
  private var lastAutoUpdateCheckTimestamp: Int64 = 0
29
29
  private var syncInProgress = false
30
30
 
31
- init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
31
+ init(bridge: CAPBridgeProtocol, config: LiveUpdateConfig, eventEmitter: LiveUpdateEventEmitter) {
32
+ self.bridge = bridge
32
33
  self.config = config
34
+ self.eventEmitter = eventEmitter
33
35
  self.httpClient = LiveUpdateHttpClient(config: config)
34
- self.plugin = plugin
35
36
  self.preferences = LiveUpdatePreferences()
36
37
  super.init()
37
38
 
@@ -178,9 +179,9 @@ import CommonCrypto
178
179
  }
179
180
 
180
181
  @objc public func ready(completion: @escaping (Result?, Error?) -> Void) {
181
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is ready.")
182
+ CAPLog.print("[", LiveUpdate.tag, "] ", "App is ready.")
182
183
  if config.readyTimeout <= 0 {
183
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Ready timeout is set to 0. Automatic rollback is disabled.")
184
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Ready timeout is set to 0. Automatic rollback is disabled.")
184
185
  }
185
186
  // Stop the rollback timer
186
187
  stopRollbackTimer()
@@ -268,7 +269,7 @@ import CommonCrypto
268
269
  // Fetch the latest bundle
269
270
  let fetchLatestBundleOptions = FetchLatestBundleOptions(channel: channel)
270
271
  guard let response = try await fetchLatestBundle(fetchLatestBundleOptions) else {
271
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "No update available.")
272
+ CAPLog.print("[", LiveUpdate.tag, "] ", "No update available.")
272
273
  return SyncResult(nextBundleId: nil)
273
274
  }
274
275
  let artifactType = response.artifactType
@@ -278,7 +279,7 @@ import CommonCrypto
278
279
  let downloadUrl = response.url
279
280
  // Check if the bundle is blocked
280
281
  if isBlockedBundleId(latestBundleId) {
281
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle is blocked and will not be downloaded.")
282
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Bundle is blocked and will not be downloaded.")
282
283
  return SyncResult(nextBundleId: nil)
283
284
  }
284
285
  // Check if bundle already exists
@@ -323,7 +324,7 @@ import CommonCrypto
323
324
 
324
325
  private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
325
326
  // Unzip the bundle
326
- let unzippedDirectory = self.unzipFile(zipFile: zipFile)
327
+ let unzippedDirectory = try self.unzipFile(zipFile: zipFile)
327
328
  // Add the bundle
328
329
  try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
329
330
  }
@@ -376,7 +377,7 @@ import CommonCrypto
376
377
  for fileToCopy in filesToCopy {
377
378
  let success = tryCopyCurrentBundleFile(fileToCopy: fileToCopy, toDirectory: toDirectory)
378
379
  if !success {
379
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to copy file: \(fileToCopy.href)")
380
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to copy file: \(fileToCopy.href)")
380
381
  // If the file could not be copied, add it to the list of missing items
381
382
  missingItems.append(fileToCopy)
382
383
  }
@@ -391,7 +392,7 @@ import CommonCrypto
391
392
  do {
392
393
  try FileManager.default.createDirectory(at: bundlesDirectoryUrl, withIntermediateDirectories: true, attributes: nil)
393
394
  } catch {
394
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to create bundles directory.")
395
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to create bundles directory.")
395
396
  }
396
397
  }
397
398
  }
@@ -423,7 +424,7 @@ import CommonCrypto
423
424
  do {
424
425
  try deleteBundleById(bundleId)
425
426
  } catch {
426
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
427
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
427
428
  }
428
429
  }
429
430
  }
@@ -436,7 +437,7 @@ import CommonCrypto
436
437
  let urlComponents = URLComponents(string: url)!
437
438
  let result = try await httpClient.download(url: urlComponents.asURL(), destination: destination, callback: callback)
438
439
  if let error = result.error {
439
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to download file: \(error)")
440
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to download file: \(error)")
440
441
  if let urlError = error.underlyingError as? URLError {
441
442
  if urlError.code == .timedOut {
442
443
  throw urlError
@@ -558,14 +559,14 @@ import CommonCrypto
558
559
  parameters["deviceId"] = getDeviceId()
559
560
  parameters["osVersion"] = await UIDevice.current.systemVersion
560
561
  parameters["platform"] = "1"
561
- parameters["pluginVersion"] = LiveUpdatePlugin.version
562
+ parameters["pluginVersion"] = LiveUpdate.version
562
563
  var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/bundles/latest")!
563
564
  urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
564
565
  let url = try urlComponents.asURL()
565
- CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle: ", url)
566
+ CAPLog.print("[", LiveUpdate.tag, "] Fetching latest bundle: ", url)
566
567
  let response = try await self.httpClient.request(url: url, type: GetLatestBundleResponse.self)
567
568
  if let data = response.data {
568
- CAPLog.print("[", LiveUpdatePlugin.tag, "] Latest bundle response: ", String(decoding: data, as: UTF8.self))
569
+ CAPLog.print("[", LiveUpdate.tag, "] Latest bundle response: ", String(decoding: data, as: UTF8.self))
569
570
  }
570
571
  if let error = response.error {
571
572
  if let urlError = error.underlyingError as? URLError {
@@ -642,7 +643,7 @@ import CommonCrypto
642
643
 
643
644
  /// - Returns: The path to the current bundle directory or `nil` if no view controller was found.
644
645
  private func getCurrentCapacitorServerPath() -> String? {
645
- guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
646
+ guard let viewController = self.bridge.viewController as? CAPBridgeViewController else {
646
647
  return nil
647
648
  }
648
649
  return viewController.getServerBasePath()
@@ -724,14 +725,14 @@ import CommonCrypto
724
725
  }
725
726
 
726
727
  private func notifyDownloadBundleProgressListeners(_ event: DownloadBundleProgressEvent) {
727
- plugin.notifyDownloadBundleProgressListeners(event)
728
+ eventEmitter.onDownloadBundleProgress(event)
728
729
  }
729
730
 
730
731
  private func performAutoUpdate() {
731
732
  // Check if enough time has passed since the last check
732
733
  let now = Int64(Date().timeIntervalSince1970 * 1000)
733
734
  if lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs {
734
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update skipped. Last check was less than 15 minutes ago.")
735
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update skipped. Last check was less than 15 minutes ago.")
735
736
  return
736
737
  }
737
738
 
@@ -741,12 +742,12 @@ import CommonCrypto
741
742
  // Run sync in background task
742
743
  Task {
743
744
  do {
744
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update started.")
745
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update started.")
745
746
  let options = SyncOptions(channel: nil)
746
747
  _ = try await sync(options)
747
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update completed successfully.")
748
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update completed successfully.")
748
749
  } catch {
749
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update failed: ", error.localizedDescription)
750
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update failed: ", error.localizedDescription)
750
751
  }
751
752
  }
752
753
  }
@@ -759,11 +760,11 @@ import CommonCrypto
759
760
  setPreviousBundleId(bundleId: currentBundleId)
760
761
  // Perform the rollback
761
762
  if let _ = currentBundleId {
762
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Rolling back to default bundle.")
763
+ CAPLog.print("[", LiveUpdate.tag, "] ", "App is not ready. Rolling back to default bundle.")
763
764
  setNextBundleById(nil)
764
765
  setCurrentBundleById(nil)
765
766
  } else {
766
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Default bundle is already in use.")
767
+ CAPLog.print("[", LiveUpdate.tag, "] ", "App is not ready. Default bundle is already in use.")
767
768
  }
768
769
  }
769
770
 
@@ -789,7 +790,7 @@ import CommonCrypto
789
790
  }
790
791
  }
791
792
  } catch {
792
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to search index.html file: \(error.localizedDescription)")
793
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to search index.html file: \(error.localizedDescription)")
793
794
  }
794
795
  return nil
795
796
  }
@@ -801,7 +802,7 @@ import CommonCrypto
801
802
  }
802
803
 
803
804
  private func setCurrentCapacitorServerPath(path: String) {
804
- guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
805
+ guard let viewController = self.bridge.viewController as? CAPBridgeViewController else {
805
806
  return
806
807
  }
807
808
  viewController.setServerBasePath(path: path)
@@ -820,11 +821,11 @@ import CommonCrypto
820
821
 
821
822
  private func notifyNextBundleSetListeners(_ bundleId: String?) {
822
823
  let event = NextBundleSetEvent(bundleId: bundleId)
823
- plugin.notifyNextBundleSetListeners(event)
824
+ eventEmitter.onNextBundleSet(event)
824
825
  }
825
826
 
826
827
  private func notifyReloadedListeners() {
827
- plugin.notifyReloadedListeners()
828
+ eventEmitter.onReloaded()
828
829
  }
829
830
 
830
831
  private func addBlockedBundleId(_ bundleId: String) {
@@ -852,7 +853,7 @@ import CommonCrypto
852
853
  let newBlockedIds = blockedList.joined(separator: ",")
853
854
  preferences.setBlockedBundleIds(newBlockedIds)
854
855
 
855
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle blocked: ", bundleId)
856
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Bundle blocked: ", bundleId)
856
857
  }
857
858
 
858
859
  private func isBlockedBundleId(_ bundleId: String) -> Bool {
@@ -882,7 +883,7 @@ import CommonCrypto
882
883
 
883
884
  if lastVersionCode == nil || lastVersionName == nil || lastVersionCode != currentVersionCode || lastVersionName != currentVersionName {
884
885
  CAPLog.print(
885
- "[", LiveUpdatePlugin.tag, "] ",
886
+ "[", LiveUpdate.tag, "] ",
886
887
  "App version changed (last: \(lastVersionName ?? "nil")/\(lastVersionCode ?? "nil"), current: \(currentVersionName)/\(currentVersionCode)), resetting config."
887
888
  )
888
889
  resetConfig()
@@ -923,9 +924,10 @@ import CommonCrypto
923
924
  }
924
925
  }
925
926
 
926
- private func unzipFile(zipFile: URL) -> URL {
927
+ private func unzipFile(zipFile: URL) throws -> URL {
927
928
  let destinationDirectory = zipFile.deletingPathExtension()
928
- SSZipArchive.unzipFile(atPath: zipFile.path, toDestination: destinationDirectory.path)
929
+ try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
930
+ try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
929
931
  return destinationDirectory
930
932
  }
931
933
 
@@ -967,7 +969,7 @@ import CommonCrypto
967
969
  .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
968
970
  .replacingOccurrences(of: "\n", with: "")
969
971
  guard let publicKeyData = Data(base64Encoded: publicKeyAsBase64) else {
970
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode public key.")
972
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to decode public key.")
971
973
  return false
972
974
  }
973
975
  let publicKeyAttributes: [CFString: Any] = [
@@ -979,12 +981,12 @@ import CommonCrypto
979
981
  var secKeyCreateWithDataError: Unmanaged<CFError>?
980
982
  guard let publicKey = SecKeyCreateWithData(publicKeyData as CFData, publicKeyAttributes as CFDictionary, &secKeyCreateWithDataError) else {
981
983
  if let error = secKeyCreateWithDataError?.takeRetainedValue() {
982
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to create public key with error: \(error)")
984
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to create public key with error: \(error)")
983
985
  }
984
986
  return false
985
987
  }
986
988
  guard let signatureData = Data(base64Encoded: signature) else {
987
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode signature.")
989
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to decode signature.")
988
990
  return false
989
991
  }
990
992
 
@@ -1014,7 +1016,7 @@ import CommonCrypto
1014
1016
  let signatureAlgorithm = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
1015
1017
  let verificationResult = SecKeyVerifySignature(publicKey, signatureAlgorithm, digest as CFData, signatureData as CFData, &secKeyVerifySignatureError)
1016
1018
  if let error = secKeyVerifySignatureError?.takeRetainedValue() {
1017
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to verify signature with error: \(error)")
1019
+ CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to verify signature with error: \(error)")
1018
1020
  }
1019
1021
  return verificationResult
1020
1022
  }
@@ -7,9 +7,8 @@ import Capacitor
7
7
  */
8
8
  @objc(LiveUpdatePlugin)
9
9
  public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
10
- public static let tag = "LiveUpdate"
11
- public static let version = "7.4.0"
12
- public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
10
+ public static let tag = LiveUpdate.tag
11
+ public static let version = LiveUpdate.version
13
12
 
14
13
  public let identifier = "LiveUpdatePlugin"
15
14
  public let jsName = "LiveUpdate"
@@ -49,8 +48,13 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
49
48
  private var implementation: LiveUpdate?
50
49
 
51
50
  override public func load() {
52
- self.config = liveUpdateConfig()
53
- self.implementation = LiveUpdate(config: config!, plugin: self)
51
+ guard let bridge = bridge else {
52
+ return
53
+ }
54
+ let config = liveUpdateConfig()
55
+ self.config = config
56
+ let eventEmitter = PluginLiveUpdateEventEmitter(plugin: self)
57
+ self.implementation = LiveUpdate(bridge: bridge, config: config, eventEmitter: eventEmitter)
54
58
 
55
59
  // Notify implementation about load
56
60
  implementation?.handleLoad()
@@ -422,4 +426,24 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
422
426
  private func resolveCall(_ call: CAPPluginCall, _ result: JSObject) {
423
427
  call.resolve(result)
424
428
  }
429
+
430
+ private class PluginLiveUpdateEventEmitter: NSObject, LiveUpdateEventEmitter {
431
+ weak var plugin: LiveUpdatePlugin?
432
+
433
+ init(plugin: LiveUpdatePlugin) {
434
+ self.plugin = plugin
435
+ }
436
+
437
+ func onDownloadBundleProgress(_ event: DownloadBundleProgressEvent) {
438
+ plugin?.notifyDownloadBundleProgressListeners(event)
439
+ }
440
+
441
+ func onNextBundleSet(_ event: NextBundleSetEvent) {
442
+ plugin?.notifyNextBundleSetListeners(event)
443
+ }
444
+
445
+ func onReloaded() {
446
+ plugin?.notifyReloadedListeners()
447
+ }
448
+ }
425
449
  }
@@ -1,6 +1,8 @@
1
1
  import Foundation
2
2
 
3
3
  public class LiveUpdatePreferences: NSObject {
4
+ private static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
5
+
4
6
  private let appIdKey = "appId" // DO NOT CHANGE
5
7
  private let blockedBundleIdsKey = "blockedBundleIds" // DO NOT CHANGE
6
8
  private let channelKey = "channel" // DO NOT CHANGE
@@ -97,6 +99,6 @@ public class LiveUpdatePreferences: NSObject {
97
99
  }
98
100
 
99
101
  private func applyPrefix(to key: String) -> String {
100
- return LiveUpdatePlugin.userDefaultsPrefix + "." + key
102
+ return LiveUpdatePreferences.userDefaultsPrefix + "." + key
101
103
  }
102
104
  }
@@ -0,0 +1,8 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public protocol LiveUpdateEventEmitter {
5
+ func onDownloadBundleProgress(_ event: DownloadBundleProgressEvent)
6
+ func onNextBundleSet(_ event: NextBundleSetEvent)
7
+ func onReloaded()
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "8.0.0",
3
+ "version": "8.1.0-dev.c088829.1767373807",
4
4
  "description": "Capacitor plugin to update your app remotely in real-time.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",