@capawesome/capacitor-live-update 8.0.0 → 8.1.0-dev.97c86c2c.1768835749
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapawesomeCapacitorLiveUpdate.podspec +2 -2
- package/Package.swift +3 -3
- package/README.md +3 -22
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +114 -57
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +23 -4
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +11 -17
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/CustomExceptions.java +33 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/LiveUpdateEventEmitter.java +13 -0
- package/ios/Plugin/LiveUpdate.swift +75 -48
- package/ios/Plugin/LiveUpdateConfig.swift +2 -0
- package/ios/Plugin/LiveUpdatePlugin.swift +52 -5
- package/ios/Plugin/LiveUpdatePreferences.swift +3 -1
- package/ios/Plugin/Protocols/LiveUpdateBridge.swift +6 -0
- package/ios/Plugin/Protocols/LiveUpdateEventEmitter.swift +8 -0
- package/package.json +1 -1
|
@@ -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 = '
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
15
|
s.dependency 'Capacitor'
|
|
16
16
|
s.dependency 'Alamofire', '~> 5.10.2'
|
|
17
|
-
s.dependency '
|
|
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(.
|
|
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/
|
|
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: "
|
|
24
|
+
.product(name: "ZIPFoundation", package: "ZIPFoundation")
|
|
25
25
|
],
|
|
26
26
|
path: "ios/Plugin"),
|
|
27
27
|
.testTarget(
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Capacitor plugin that allows you to update your app remotely in real-time withou
|
|
|
13
13
|
We are proud to offer one of the most complete and feature-rich Capacitor plugins for Over-the-Air (OTA) updates. Here are some of the key features:
|
|
14
14
|
|
|
15
15
|
- 🔋 Supports **Android and iOS**
|
|
16
|
-
- ⚡️ **Capacitor 7** support
|
|
16
|
+
- ⚡️ **Capacitor 6/7/8** support
|
|
17
17
|
- 📦 **Bundle Management**: Download, set, and delete bundles.
|
|
18
18
|
- ☁️ **Cloud Support**: Use the [Capawesome Cloud](https://cloud.capawesome.io/) to manage your app updates.
|
|
19
19
|
- 📺 **Channel Support**: Set a channel for the app to manage different versions.
|
|
@@ -38,12 +38,8 @@ Missing a feature? Just [open an issue](https://github.com/capawesome-team/capac
|
|
|
38
38
|
|
|
39
39
|
## Guides
|
|
40
40
|
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
- [How Live Updates for Capacitor work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/)
|
|
44
|
-
- [How to gradually roll out Capacitor Live Updates](https://capawesome.io/blog/how-to-gradually-roll-out-capacitor-live-updates/#update-a-bundle)
|
|
45
|
-
- [How to restrict Capacitor Live Updates to Native Versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/)
|
|
46
|
-
- [Live Updates for Nuxt Capacitor Apps with Capawesome Cloud](https://capawesome.io/blog/live-updates-for-nuxt-capacitor/)
|
|
41
|
+
- [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/cloud/live-updates/setup/)
|
|
42
|
+
- [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/)
|
|
47
43
|
- [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
|
|
48
44
|
|
|
49
45
|
## Installation
|
|
@@ -93,21 +89,6 @@ Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Priva
|
|
|
93
89
|
|
|
94
90
|
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
91
|
|
|
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
92
|
## Configuration
|
|
112
93
|
|
|
113
94
|
<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,32 @@ 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
|
+
|
|
86
|
+
@Nullable
|
|
87
|
+
private static LiveUpdate instance = null;
|
|
88
|
+
|
|
89
|
+
private static final Object LOCK = new Object();
|
|
90
|
+
|
|
80
91
|
private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
81
92
|
|
|
93
|
+
@NonNull
|
|
94
|
+
private final Bridge bridge;
|
|
95
|
+
|
|
82
96
|
@NonNull
|
|
83
97
|
private final LiveUpdateConfig config;
|
|
84
98
|
|
|
99
|
+
@NonNull
|
|
100
|
+
private final Context context;
|
|
101
|
+
|
|
85
102
|
private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
|
|
86
103
|
|
|
87
|
-
@
|
|
88
|
-
private final
|
|
104
|
+
@Nullable
|
|
105
|
+
private final LiveUpdateEventEmitter eventEmitter;
|
|
89
106
|
|
|
90
107
|
@NonNull
|
|
91
|
-
private final
|
|
108
|
+
private final LiveUpdateHttpClient httpClient;
|
|
92
109
|
|
|
93
110
|
@NonNull
|
|
94
111
|
private final LiveUpdatePreferences preferences;
|
|
@@ -105,12 +122,19 @@ public class LiveUpdate {
|
|
|
105
122
|
private boolean rollbackPerformed = false;
|
|
106
123
|
private boolean syncInProgress = false;
|
|
107
124
|
|
|
108
|
-
|
|
125
|
+
private LiveUpdate(
|
|
126
|
+
@NonNull Context context,
|
|
127
|
+
@NonNull Bridge bridge,
|
|
128
|
+
@NonNull LiveUpdateConfig config,
|
|
129
|
+
@Nullable LiveUpdateEventEmitter eventEmitter
|
|
130
|
+
) throws PackageManager.NameNotFoundException {
|
|
131
|
+
this.context = context;
|
|
132
|
+
this.bridge = bridge;
|
|
109
133
|
this.config = config;
|
|
134
|
+
this.eventEmitter = eventEmitter;
|
|
110
135
|
this.httpClient = new LiveUpdateHttpClient(config);
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
136
|
+
this.preferences = new LiveUpdatePreferences(context);
|
|
137
|
+
this.webViewSettingsEditor = context.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
114
138
|
|
|
115
139
|
// Check version and reset config if version changed
|
|
116
140
|
checkAndResetConfigIfVersionChanged();
|
|
@@ -120,6 +144,31 @@ public class LiveUpdate {
|
|
|
120
144
|
startRollbackTimer();
|
|
121
145
|
}
|
|
122
146
|
|
|
147
|
+
@NonNull
|
|
148
|
+
public static LiveUpdate getInstance(
|
|
149
|
+
@NonNull Context context,
|
|
150
|
+
@NonNull Bridge bridge,
|
|
151
|
+
@NonNull LiveUpdateConfig config,
|
|
152
|
+
@Nullable LiveUpdateEventEmitter eventEmitter
|
|
153
|
+
) throws PackageManager.NameNotFoundException {
|
|
154
|
+
if (instance == null) {
|
|
155
|
+
synchronized (LOCK) {
|
|
156
|
+
if (instance == null) {
|
|
157
|
+
instance = new LiveUpdate(context, bridge, config, eventEmitter);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return instance;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@NonNull
|
|
165
|
+
public static LiveUpdate getInstance() {
|
|
166
|
+
if (instance == null) {
|
|
167
|
+
throw new IllegalStateException("LiveUpdate not initialized. Call getInstance with dependencies first.");
|
|
168
|
+
}
|
|
169
|
+
return instance;
|
|
170
|
+
}
|
|
171
|
+
|
|
123
172
|
public void clearBlockedBundles() {
|
|
124
173
|
preferences.setBlockedBundleIds(null);
|
|
125
174
|
}
|
|
@@ -128,7 +177,7 @@ public class LiveUpdate {
|
|
|
128
177
|
String bundleId = options.getBundleId();
|
|
129
178
|
|
|
130
179
|
if (!hasBundleById(bundleId)) {
|
|
131
|
-
Exception exception =
|
|
180
|
+
Exception exception = CustomExceptions.BUNDLE_NOT_FOUND;
|
|
132
181
|
callback.error(exception);
|
|
133
182
|
return;
|
|
134
183
|
}
|
|
@@ -284,9 +333,9 @@ public class LiveUpdate {
|
|
|
284
333
|
}
|
|
285
334
|
|
|
286
335
|
public void ready(@NonNull NonEmptyCallback callback) {
|
|
287
|
-
Logger.debug(
|
|
336
|
+
Logger.debug(LiveUpdate.TAG, "App is ready.");
|
|
288
337
|
if (config.getReadyTimeout() <= 0) {
|
|
289
|
-
Logger.warn(
|
|
338
|
+
Logger.warn(LiveUpdate.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
|
|
290
339
|
}
|
|
291
340
|
// Stop the rollback timer
|
|
292
341
|
stopRollbackTimer();
|
|
@@ -352,7 +401,7 @@ public class LiveUpdate {
|
|
|
352
401
|
if (hasBundleById(bundleId)) {
|
|
353
402
|
setNextBundleById(bundleId);
|
|
354
403
|
} else {
|
|
355
|
-
Exception exception =
|
|
404
|
+
Exception exception = CustomExceptions.BUNDLE_NOT_FOUND;
|
|
356
405
|
callback.error(exception);
|
|
357
406
|
return;
|
|
358
407
|
}
|
|
@@ -362,7 +411,7 @@ public class LiveUpdate {
|
|
|
362
411
|
|
|
363
412
|
public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback) {
|
|
364
413
|
if (syncInProgress) {
|
|
365
|
-
Exception exception =
|
|
414
|
+
Exception exception = CustomExceptions.SYNC_IN_PROGRESS;
|
|
366
415
|
callback.error(exception);
|
|
367
416
|
return;
|
|
368
417
|
}
|
|
@@ -378,7 +427,7 @@ public class LiveUpdate {
|
|
|
378
427
|
public void success(@Nullable GetLatestBundleResponse response) {
|
|
379
428
|
try {
|
|
380
429
|
if (response == null) {
|
|
381
|
-
Logger.debug(
|
|
430
|
+
Logger.debug(LiveUpdate.TAG, "No update available.");
|
|
382
431
|
syncInProgress = false;
|
|
383
432
|
SyncResult syncResult = new SyncResult(null);
|
|
384
433
|
callback.success(syncResult);
|
|
@@ -391,7 +440,7 @@ public class LiveUpdate {
|
|
|
391
440
|
String url = response.getUrl();
|
|
392
441
|
// Check if the bundle is blocked
|
|
393
442
|
if (isBlockedBundleId(latestBundleId)) {
|
|
394
|
-
Logger.warn(
|
|
443
|
+
Logger.warn(LiveUpdate.TAG, "Bundle is blocked and will not be downloaded.");
|
|
395
444
|
syncInProgress = false;
|
|
396
445
|
SyncResult syncResult = new SyncResult(null);
|
|
397
446
|
callback.success(syncResult);
|
|
@@ -459,7 +508,7 @@ public class LiveUpdate {
|
|
|
459
508
|
// Search folder with index.html file
|
|
460
509
|
File indexHtmlFile = searchIndexHtmlFile(sourceDirectory);
|
|
461
510
|
if (indexHtmlFile == null) {
|
|
462
|
-
throw
|
|
511
|
+
throw CustomExceptions.BUNDLE_INDEX_HTML_MISSING;
|
|
463
512
|
}
|
|
464
513
|
|
|
465
514
|
// Create the bundles directory if it does not exist
|
|
@@ -482,21 +531,21 @@ public class LiveUpdate {
|
|
|
482
531
|
}
|
|
483
532
|
|
|
484
533
|
private File buildBundlesDirectory() {
|
|
485
|
-
return new File(
|
|
534
|
+
return new File(context.getFilesDir(), bundlesDirectory);
|
|
486
535
|
}
|
|
487
536
|
|
|
488
537
|
private File buildBundleDirectoryFor(@NonNull String bundleId) {
|
|
489
|
-
return new File(
|
|
538
|
+
return new File(context.getFilesDir(), bundlesDirectory + "/" + bundleId);
|
|
490
539
|
}
|
|
491
540
|
|
|
492
541
|
private File buildTemporaryDirectory() {
|
|
493
542
|
String fileName = UUID.randomUUID().toString();
|
|
494
|
-
return new File(
|
|
543
|
+
return new File(context.getCacheDir(), fileName);
|
|
495
544
|
}
|
|
496
545
|
|
|
497
546
|
private File buildTemporaryZipFile() {
|
|
498
547
|
String fileName = UUID.randomUUID().toString() + ".zip";
|
|
499
|
-
return new File(
|
|
548
|
+
return new File(context.getCacheDir(), fileName);
|
|
500
549
|
}
|
|
501
550
|
|
|
502
551
|
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
@@ -504,7 +553,7 @@ public class LiveUpdate {
|
|
|
504
553
|
String currentBundleId = getCurrentBundleId();
|
|
505
554
|
if (currentBundleId == null) {
|
|
506
555
|
// Create the source input stream
|
|
507
|
-
AssetManager assets =
|
|
556
|
+
AssetManager assets = context.getAssets();
|
|
508
557
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
509
558
|
// Create the destination file
|
|
510
559
|
File destination = new File(destinationDirectory, href);
|
|
@@ -532,7 +581,7 @@ public class LiveUpdate {
|
|
|
532
581
|
for (ManifestItem fileToCopy : filesToCopy) {
|
|
533
582
|
boolean success = tryCopyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
534
583
|
if (!success) {
|
|
535
|
-
Logger.warn(
|
|
584
|
+
Logger.warn(LiveUpdate.TAG, "Failed to copy file: " + fileToCopy.getHref());
|
|
536
585
|
// If the file could not be copied, add it to the list of missing items
|
|
537
586
|
missingItems.add(fileToCopy);
|
|
538
587
|
}
|
|
@@ -578,8 +627,8 @@ public class LiveUpdate {
|
|
|
578
627
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
579
628
|
return keyFactory.generatePublic(X509publicKey);
|
|
580
629
|
} catch (Exception exception) {
|
|
581
|
-
Logger.error(
|
|
582
|
-
throw
|
|
630
|
+
Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
|
|
631
|
+
throw CustomExceptions.PUBLIC_KEY_INVALID;
|
|
583
632
|
}
|
|
584
633
|
}
|
|
585
634
|
|
|
@@ -645,8 +694,8 @@ public class LiveUpdate {
|
|
|
645
694
|
completionCallback.success();
|
|
646
695
|
} else {
|
|
647
696
|
String errorMessage = response.body().string();
|
|
648
|
-
Exception exception =
|
|
649
|
-
Logger.error(
|
|
697
|
+
Exception exception = CustomExceptions.DOWNLOAD_FAILED;
|
|
698
|
+
Logger.error(LiveUpdate.TAG, errorMessage, exception);
|
|
650
699
|
completionCallback.error(exception);
|
|
651
700
|
}
|
|
652
701
|
} catch (Exception e) {
|
|
@@ -749,7 +798,7 @@ public class LiveUpdate {
|
|
|
749
798
|
public void error(@NonNull Exception e) {
|
|
750
799
|
// Capture first error and cancel all remaining downloads
|
|
751
800
|
if (firstError.compareAndSet(null, e)) {
|
|
752
|
-
Logger.error(
|
|
801
|
+
Logger.error(LiveUpdate.TAG, "Failed to download file: " + item.getHref(), e);
|
|
753
802
|
// Cancel all in-flight downloads (fail-fast)
|
|
754
803
|
synchronized (activeCalls) {
|
|
755
804
|
for (Call activeCall : activeCalls) {
|
|
@@ -924,10 +973,10 @@ public class LiveUpdate {
|
|
|
924
973
|
.addQueryParameter("deviceId", getDeviceId())
|
|
925
974
|
.addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
|
|
926
975
|
.addQueryParameter("platform", "0")
|
|
927
|
-
.addQueryParameter("pluginVersion",
|
|
976
|
+
.addQueryParameter("pluginVersion", LiveUpdate.VERSION)
|
|
928
977
|
.build()
|
|
929
978
|
.toString();
|
|
930
|
-
Logger.debug(
|
|
979
|
+
Logger.debug(LiveUpdate.TAG, "Fetching latest bundle: " + url);
|
|
931
980
|
|
|
932
981
|
httpClient.enqueue(
|
|
933
982
|
url,
|
|
@@ -936,7 +985,7 @@ public class LiveUpdate {
|
|
|
936
985
|
public void success(@NonNull Response response) {
|
|
937
986
|
try {
|
|
938
987
|
String responseBodyString = response.body().string();
|
|
939
|
-
Logger.debug(
|
|
988
|
+
Logger.debug(LiveUpdate.TAG, "Latest bundle response: " + responseBodyString);
|
|
940
989
|
if (response.isSuccessful()) {
|
|
941
990
|
JSONObject responseJson = new JSONObject(responseBodyString);
|
|
942
991
|
GetLatestBundleResponse result = new GetLatestBundleResponse(responseJson);
|
|
@@ -986,8 +1035,8 @@ public class LiveUpdate {
|
|
|
986
1035
|
source.close();
|
|
987
1036
|
return digest.digest();
|
|
988
1037
|
} catch (IOException exception) {
|
|
989
|
-
Logger.error(
|
|
990
|
-
throw
|
|
1038
|
+
Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
|
|
1039
|
+
throw CustomExceptions.CHECKSUM_CALCULATION_FAILED;
|
|
991
1040
|
}
|
|
992
1041
|
}
|
|
993
1042
|
|
|
@@ -1037,7 +1086,7 @@ public class LiveUpdate {
|
|
|
1037
1086
|
* @return The absolute path to the current bundle directory (`public` for the built-in bundle).
|
|
1038
1087
|
*/
|
|
1039
1088
|
private String getCurrentCapacitorServerPath() {
|
|
1040
|
-
return
|
|
1089
|
+
return bridge.getServerBasePath();
|
|
1041
1090
|
}
|
|
1042
1091
|
|
|
1043
1092
|
@NonNull
|
|
@@ -1067,8 +1116,7 @@ public class LiveUpdate {
|
|
|
1067
1116
|
*/
|
|
1068
1117
|
@NonNull
|
|
1069
1118
|
private String getNextCapacitorServerPath() {
|
|
1070
|
-
String path =
|
|
1071
|
-
.getContext()
|
|
1119
|
+
String path = context
|
|
1072
1120
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
1073
1121
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
1074
1122
|
// Empty path means default path
|
|
@@ -1113,7 +1161,7 @@ public class LiveUpdate {
|
|
|
1113
1161
|
private Manifest loadCurrentManifest() throws Exception {
|
|
1114
1162
|
String currentBundleId = getCurrentBundleId();
|
|
1115
1163
|
if (currentBundleId == null) {
|
|
1116
|
-
AssetManager assets =
|
|
1164
|
+
AssetManager assets = context.getAssets();
|
|
1117
1165
|
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
1118
1166
|
if (manifestFileExists) {
|
|
1119
1167
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
@@ -1145,14 +1193,17 @@ public class LiveUpdate {
|
|
|
1145
1193
|
}
|
|
1146
1194
|
|
|
1147
1195
|
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
1148
|
-
|
|
1196
|
+
if (eventEmitter == null) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
eventEmitter.onDownloadBundleProgress(event);
|
|
1149
1200
|
}
|
|
1150
1201
|
|
|
1151
1202
|
private void performAutoUpdate() {
|
|
1152
1203
|
// Check if enough time has passed since the last check
|
|
1153
1204
|
long now = System.currentTimeMillis();
|
|
1154
1205
|
if (lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs) {
|
|
1155
|
-
Logger.debug(
|
|
1206
|
+
Logger.debug(LiveUpdate.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
|
|
1156
1207
|
return;
|
|
1157
1208
|
}
|
|
1158
1209
|
|
|
@@ -1160,17 +1211,17 @@ public class LiveUpdate {
|
|
|
1160
1211
|
lastAutoUpdateCheckTimestamp = now;
|
|
1161
1212
|
|
|
1162
1213
|
// Run sync
|
|
1163
|
-
Logger.debug(
|
|
1214
|
+
Logger.debug(LiveUpdate.TAG, "Auto-update started.");
|
|
1164
1215
|
SyncOptions options = new SyncOptions((String) null);
|
|
1165
1216
|
NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
|
|
1166
1217
|
@Override
|
|
1167
1218
|
public void success(@NonNull Result result) {
|
|
1168
|
-
Logger.debug(
|
|
1219
|
+
Logger.debug(LiveUpdate.TAG, "Auto-update completed successfully.");
|
|
1169
1220
|
}
|
|
1170
1221
|
|
|
1171
1222
|
@Override
|
|
1172
1223
|
public void error(@NonNull Exception exception) {
|
|
1173
|
-
Logger.error(
|
|
1224
|
+
Logger.error(LiveUpdate.TAG, "Auto-update failed: " + exception.getMessage(), exception);
|
|
1174
1225
|
}
|
|
1175
1226
|
};
|
|
1176
1227
|
sync(options, callback);
|
|
@@ -1184,9 +1235,9 @@ public class LiveUpdate {
|
|
|
1184
1235
|
setPreviousBundleId(currentBundleId);
|
|
1185
1236
|
// Log the rollback result
|
|
1186
1237
|
if (currentBundleId == null) {
|
|
1187
|
-
Logger.debug(
|
|
1238
|
+
Logger.debug(LiveUpdate.TAG, "App is not ready. Default bundle is already in use.");
|
|
1188
1239
|
} else {
|
|
1189
|
-
Logger.debug(
|
|
1240
|
+
Logger.debug(LiveUpdate.TAG, "App is not ready. Rolling back to default bundle.");
|
|
1190
1241
|
// Rollback to the default bundle
|
|
1191
1242
|
setNextBundleById(null);
|
|
1192
1243
|
setCurrentBundleById(null);
|
|
@@ -1232,11 +1283,11 @@ public class LiveUpdate {
|
|
|
1232
1283
|
|
|
1233
1284
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
1234
1285
|
if (path.equals(defaultWebAssetDir)) {
|
|
1235
|
-
this.
|
|
1286
|
+
this.bridge.setServerAssetPath(path);
|
|
1236
1287
|
} else {
|
|
1237
|
-
this.
|
|
1288
|
+
this.bridge.setServerBasePath(path);
|
|
1238
1289
|
}
|
|
1239
|
-
this.
|
|
1290
|
+
this.bridge.reload();
|
|
1240
1291
|
// Notify listeners
|
|
1241
1292
|
notifyReloadedListeners();
|
|
1242
1293
|
}
|
|
@@ -1257,12 +1308,18 @@ public class LiveUpdate {
|
|
|
1257
1308
|
}
|
|
1258
1309
|
|
|
1259
1310
|
private void notifyNextBundleSetListeners(@Nullable String bundleId) {
|
|
1311
|
+
if (eventEmitter == null) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1260
1314
|
NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
|
|
1261
|
-
|
|
1315
|
+
eventEmitter.onNextBundleSet(event);
|
|
1262
1316
|
}
|
|
1263
1317
|
|
|
1264
1318
|
private void notifyReloadedListeners() {
|
|
1265
|
-
|
|
1319
|
+
if (eventEmitter == null) {
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
eventEmitter.onReloaded();
|
|
1266
1323
|
}
|
|
1267
1324
|
|
|
1268
1325
|
private void addBlockedBundleId(@NonNull String bundleId) {
|
|
@@ -1292,7 +1349,7 @@ public class LiveUpdate {
|
|
|
1292
1349
|
String newBlockedIds = String.join(",", blockedList);
|
|
1293
1350
|
preferences.setBlockedBundleIds(newBlockedIds);
|
|
1294
1351
|
|
|
1295
|
-
Logger.debug(
|
|
1352
|
+
Logger.debug(LiveUpdate.TAG, "Bundle blocked: " + bundleId);
|
|
1296
1353
|
}
|
|
1297
1354
|
|
|
1298
1355
|
private boolean isBlockedBundleId(@NonNull String bundleId) {
|
|
@@ -1316,7 +1373,7 @@ public class LiveUpdate {
|
|
|
1316
1373
|
|
|
1317
1374
|
if (lastVersionCode == -1 || lastVersionCode != currentVersionCode) {
|
|
1318
1375
|
Logger.debug(
|
|
1319
|
-
|
|
1376
|
+
LiveUpdate.TAG,
|
|
1320
1377
|
"App version changed (last: " + lastVersionCode + ", current: " + currentVersionCode + "), resetting config."
|
|
1321
1378
|
);
|
|
1322
1379
|
resetConfig();
|
|
@@ -1366,12 +1423,12 @@ public class LiveUpdate {
|
|
|
1366
1423
|
if (publicKey != null) {
|
|
1367
1424
|
// Verify the signature
|
|
1368
1425
|
if (signature == null) {
|
|
1369
|
-
throw
|
|
1426
|
+
throw CustomExceptions.SIGNATURE_MISSING;
|
|
1370
1427
|
}
|
|
1371
1428
|
// Verify the signature
|
|
1372
1429
|
boolean verified = verifySignatureForFile(file, signature, publicKey);
|
|
1373
1430
|
if (!verified) {
|
|
1374
|
-
throw
|
|
1431
|
+
throw CustomExceptions.SIGNATURE_VERIFICATION_FAILED;
|
|
1375
1432
|
}
|
|
1376
1433
|
}
|
|
1377
1434
|
// Verify the checksum
|
|
@@ -1379,7 +1436,7 @@ public class LiveUpdate {
|
|
|
1379
1436
|
// Calculate the checksum
|
|
1380
1437
|
boolean verified = verifyChecksumForFile(file, checksum);
|
|
1381
1438
|
if (!verified) {
|
|
1382
|
-
throw
|
|
1439
|
+
throw CustomExceptions.CHECKSUM_MISMATCH;
|
|
1383
1440
|
}
|
|
1384
1441
|
}
|
|
1385
1442
|
}
|
|
@@ -1402,17 +1459,17 @@ public class LiveUpdate {
|
|
|
1402
1459
|
source.close();
|
|
1403
1460
|
return sig.verify(signatureBytes);
|
|
1404
1461
|
} catch (Exception exception) {
|
|
1405
|
-
Logger.error(
|
|
1406
|
-
throw
|
|
1462
|
+
Logger.error(LiveUpdate.TAG, exception.getMessage(), exception);
|
|
1463
|
+
throw CustomExceptions.SIGNATURE_VERIFICATION_FAILED;
|
|
1407
1464
|
}
|
|
1408
1465
|
}
|
|
1409
1466
|
|
|
1410
1467
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
1411
|
-
String packageName = this.
|
|
1468
|
+
String packageName = this.context.getPackageName();
|
|
1412
1469
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1413
|
-
return this.
|
|
1470
|
+
return this.context.getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
|
|
1414
1471
|
} else {
|
|
1415
|
-
return this.
|
|
1472
|
+
return this.context.getPackageManager().getPackageInfo(packageName, 0);
|
|
1416
1473
|
}
|
|
1417
1474
|
}
|
|
1418
1475
|
}
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java
CHANGED
|
@@ -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 =
|
|
38
|
-
public static final String VERSION =
|
|
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
|
-
|
|
71
|
+
LiveUpdateEventEmitter eventEmitter = new PluginLiveUpdateEventEmitter();
|
|
72
|
+
implementation = LiveUpdate.getInstance(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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,29 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import CryptoKit
|
|
3
|
-
|
|
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
|
+
|
|
13
|
+
private static var _sharedInstance: LiveUpdate?
|
|
14
|
+
private static let queue = DispatchQueue(label: "io.capawesome.liveupdate.singleton")
|
|
15
|
+
|
|
14
16
|
private let autoUpdateIntervalMs: Int64 = 15 * 60 * 1000 // 15 minutes
|
|
17
|
+
private let bridge: LiveUpdateBridge
|
|
15
18
|
private let bundlesDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE! (See https://dub.sh/BLluidt)
|
|
16
19
|
private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
17
20
|
private let config: LiveUpdateConfig
|
|
18
21
|
private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
|
|
19
22
|
private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
|
|
23
|
+
private let eventEmitter: LiveUpdateEventEmitter?
|
|
20
24
|
private let httpClient: LiveUpdateHttpClient
|
|
21
25
|
private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
22
26
|
private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
|
|
23
|
-
private let plugin: LiveUpdatePlugin
|
|
24
27
|
private let preferences: LiveUpdatePreferences
|
|
25
28
|
|
|
26
29
|
private var rollbackDispatchWorkItem: DispatchWorkItem?
|
|
@@ -28,10 +31,11 @@ import CommonCrypto
|
|
|
28
31
|
private var lastAutoUpdateCheckTimestamp: Int64 = 0
|
|
29
32
|
private var syncInProgress = false
|
|
30
33
|
|
|
31
|
-
init(config: LiveUpdateConfig,
|
|
34
|
+
private init(bridge: LiveUpdateBridge, config: LiveUpdateConfig, eventEmitter: LiveUpdateEventEmitter?) {
|
|
35
|
+
self.bridge = bridge
|
|
32
36
|
self.config = config
|
|
37
|
+
self.eventEmitter = eventEmitter
|
|
33
38
|
self.httpClient = LiveUpdateHttpClient(config: config)
|
|
34
|
-
self.plugin = plugin
|
|
35
39
|
self.preferences = LiveUpdatePreferences()
|
|
36
40
|
super.init()
|
|
37
41
|
|
|
@@ -43,6 +47,28 @@ import CommonCrypto
|
|
|
43
47
|
startRollbackTimer()
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
public static func getInstance(
|
|
51
|
+
bridge: LiveUpdateBridge,
|
|
52
|
+
config: LiveUpdateConfig,
|
|
53
|
+
eventEmitter: LiveUpdateEventEmitter?
|
|
54
|
+
) -> LiveUpdate {
|
|
55
|
+
return queue.sync {
|
|
56
|
+
if _sharedInstance == nil {
|
|
57
|
+
_sharedInstance = LiveUpdate(bridge: bridge, config: config, eventEmitter: eventEmitter)
|
|
58
|
+
}
|
|
59
|
+
return _sharedInstance!
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static func getInstance() -> LiveUpdate {
|
|
64
|
+
return queue.sync {
|
|
65
|
+
guard let instance = _sharedInstance else {
|
|
66
|
+
fatalError("LiveUpdate not initialized. Call getInstance(bridge:config:eventEmitter:) first.")
|
|
67
|
+
}
|
|
68
|
+
return instance
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
@objc public func clearBlockedBundles() {
|
|
47
73
|
preferences.setBlockedBundleIds(nil)
|
|
48
74
|
}
|
|
@@ -178,9 +204,9 @@ import CommonCrypto
|
|
|
178
204
|
}
|
|
179
205
|
|
|
180
206
|
@objc public func ready(completion: @escaping (Result?, Error?) -> Void) {
|
|
181
|
-
CAPLog.print("[",
|
|
207
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "App is ready.")
|
|
182
208
|
if config.readyTimeout <= 0 {
|
|
183
|
-
CAPLog.print("[",
|
|
209
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Ready timeout is set to 0. Automatic rollback is disabled.")
|
|
184
210
|
}
|
|
185
211
|
// Stop the rollback timer
|
|
186
212
|
stopRollbackTimer()
|
|
@@ -268,7 +294,7 @@ import CommonCrypto
|
|
|
268
294
|
// Fetch the latest bundle
|
|
269
295
|
let fetchLatestBundleOptions = FetchLatestBundleOptions(channel: channel)
|
|
270
296
|
guard let response = try await fetchLatestBundle(fetchLatestBundleOptions) else {
|
|
271
|
-
CAPLog.print("[",
|
|
297
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "No update available.")
|
|
272
298
|
return SyncResult(nextBundleId: nil)
|
|
273
299
|
}
|
|
274
300
|
let artifactType = response.artifactType
|
|
@@ -278,7 +304,7 @@ import CommonCrypto
|
|
|
278
304
|
let downloadUrl = response.url
|
|
279
305
|
// Check if the bundle is blocked
|
|
280
306
|
if isBlockedBundleId(latestBundleId) {
|
|
281
|
-
CAPLog.print("[",
|
|
307
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Bundle is blocked and will not be downloaded.")
|
|
282
308
|
return SyncResult(nextBundleId: nil)
|
|
283
309
|
}
|
|
284
310
|
// Check if bundle already exists
|
|
@@ -323,7 +349,7 @@ import CommonCrypto
|
|
|
323
349
|
|
|
324
350
|
private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
|
|
325
351
|
// Unzip the bundle
|
|
326
|
-
let unzippedDirectory = self.unzipFile(zipFile: zipFile)
|
|
352
|
+
let unzippedDirectory = try self.unzipFile(zipFile: zipFile)
|
|
327
353
|
// Add the bundle
|
|
328
354
|
try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
|
|
329
355
|
}
|
|
@@ -376,7 +402,7 @@ import CommonCrypto
|
|
|
376
402
|
for fileToCopy in filesToCopy {
|
|
377
403
|
let success = tryCopyCurrentBundleFile(fileToCopy: fileToCopy, toDirectory: toDirectory)
|
|
378
404
|
if !success {
|
|
379
|
-
CAPLog.print("[",
|
|
405
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to copy file: \(fileToCopy.href)")
|
|
380
406
|
// If the file could not be copied, add it to the list of missing items
|
|
381
407
|
missingItems.append(fileToCopy)
|
|
382
408
|
}
|
|
@@ -391,7 +417,7 @@ import CommonCrypto
|
|
|
391
417
|
do {
|
|
392
418
|
try FileManager.default.createDirectory(at: bundlesDirectoryUrl, withIntermediateDirectories: true, attributes: nil)
|
|
393
419
|
} catch {
|
|
394
|
-
CAPLog.print("[",
|
|
420
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to create bundles directory.")
|
|
395
421
|
}
|
|
396
422
|
}
|
|
397
423
|
}
|
|
@@ -423,7 +449,7 @@ import CommonCrypto
|
|
|
423
449
|
do {
|
|
424
450
|
try deleteBundleById(bundleId)
|
|
425
451
|
} catch {
|
|
426
|
-
CAPLog.print("[",
|
|
452
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
|
|
427
453
|
}
|
|
428
454
|
}
|
|
429
455
|
}
|
|
@@ -436,7 +462,7 @@ import CommonCrypto
|
|
|
436
462
|
let urlComponents = URLComponents(string: url)!
|
|
437
463
|
let result = try await httpClient.download(url: urlComponents.asURL(), destination: destination, callback: callback)
|
|
438
464
|
if let error = result.error {
|
|
439
|
-
CAPLog.print("[",
|
|
465
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to download file: \(error)")
|
|
440
466
|
if let urlError = error.underlyingError as? URLError {
|
|
441
467
|
if urlError.code == .timedOut {
|
|
442
468
|
throw urlError
|
|
@@ -558,14 +584,14 @@ import CommonCrypto
|
|
|
558
584
|
parameters["deviceId"] = getDeviceId()
|
|
559
585
|
parameters["osVersion"] = await UIDevice.current.systemVersion
|
|
560
586
|
parameters["platform"] = "1"
|
|
561
|
-
parameters["pluginVersion"] =
|
|
587
|
+
parameters["pluginVersion"] = LiveUpdate.version
|
|
562
588
|
var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/bundles/latest")!
|
|
563
589
|
urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
|
|
564
590
|
let url = try urlComponents.asURL()
|
|
565
|
-
CAPLog.print("[",
|
|
591
|
+
CAPLog.print("[", LiveUpdate.tag, "] Fetching latest bundle: ", url)
|
|
566
592
|
let response = try await self.httpClient.request(url: url, type: GetLatestBundleResponse.self)
|
|
567
593
|
if let data = response.data {
|
|
568
|
-
CAPLog.print("[",
|
|
594
|
+
CAPLog.print("[", LiveUpdate.tag, "] Latest bundle response: ", String(decoding: data, as: UTF8.self))
|
|
569
595
|
}
|
|
570
596
|
if let error = response.error {
|
|
571
597
|
if let urlError = error.underlyingError as? URLError {
|
|
@@ -640,12 +666,9 @@ import CommonCrypto
|
|
|
640
666
|
return bundleId
|
|
641
667
|
}
|
|
642
668
|
|
|
643
|
-
/// - Returns: The path to the current bundle directory or `nil` if
|
|
669
|
+
/// - Returns: The path to the current bundle directory or `nil` if the bridge does not provide a path.
|
|
644
670
|
private func getCurrentCapacitorServerPath() -> String? {
|
|
645
|
-
|
|
646
|
-
return nil
|
|
647
|
-
}
|
|
648
|
-
return viewController.getServerBasePath()
|
|
671
|
+
return bridge.getServerBasePath()
|
|
649
672
|
}
|
|
650
673
|
|
|
651
674
|
private func getDeviceId() -> String {
|
|
@@ -724,14 +747,16 @@ import CommonCrypto
|
|
|
724
747
|
}
|
|
725
748
|
|
|
726
749
|
private func notifyDownloadBundleProgressListeners(_ event: DownloadBundleProgressEvent) {
|
|
727
|
-
|
|
750
|
+
if let eventEmitter = eventEmitter {
|
|
751
|
+
eventEmitter.onDownloadBundleProgress(event)
|
|
752
|
+
}
|
|
728
753
|
}
|
|
729
754
|
|
|
730
755
|
private func performAutoUpdate() {
|
|
731
756
|
// Check if enough time has passed since the last check
|
|
732
757
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
733
758
|
if lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs {
|
|
734
|
-
CAPLog.print("[",
|
|
759
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update skipped. Last check was less than 15 minutes ago.")
|
|
735
760
|
return
|
|
736
761
|
}
|
|
737
762
|
|
|
@@ -741,12 +766,12 @@ import CommonCrypto
|
|
|
741
766
|
// Run sync in background task
|
|
742
767
|
Task {
|
|
743
768
|
do {
|
|
744
|
-
CAPLog.print("[",
|
|
769
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update started.")
|
|
745
770
|
let options = SyncOptions(channel: nil)
|
|
746
771
|
_ = try await sync(options)
|
|
747
|
-
CAPLog.print("[",
|
|
772
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update completed successfully.")
|
|
748
773
|
} catch {
|
|
749
|
-
CAPLog.print("[",
|
|
774
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Auto-update failed: ", error.localizedDescription)
|
|
750
775
|
}
|
|
751
776
|
}
|
|
752
777
|
}
|
|
@@ -759,11 +784,11 @@ import CommonCrypto
|
|
|
759
784
|
setPreviousBundleId(bundleId: currentBundleId)
|
|
760
785
|
// Perform the rollback
|
|
761
786
|
if let _ = currentBundleId {
|
|
762
|
-
CAPLog.print("[",
|
|
787
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "App is not ready. Rolling back to default bundle.")
|
|
763
788
|
setNextBundleById(nil)
|
|
764
789
|
setCurrentBundleById(nil)
|
|
765
790
|
} else {
|
|
766
|
-
CAPLog.print("[",
|
|
791
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "App is not ready. Default bundle is already in use.")
|
|
767
792
|
}
|
|
768
793
|
}
|
|
769
794
|
|
|
@@ -789,7 +814,7 @@ import CommonCrypto
|
|
|
789
814
|
}
|
|
790
815
|
}
|
|
791
816
|
} catch {
|
|
792
|
-
CAPLog.print("[",
|
|
817
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to search index.html file: \(error.localizedDescription)")
|
|
793
818
|
}
|
|
794
819
|
return nil
|
|
795
820
|
}
|
|
@@ -801,10 +826,7 @@ import CommonCrypto
|
|
|
801
826
|
}
|
|
802
827
|
|
|
803
828
|
private func setCurrentCapacitorServerPath(path: String) {
|
|
804
|
-
|
|
805
|
-
return
|
|
806
|
-
}
|
|
807
|
-
viewController.setServerBasePath(path: path)
|
|
829
|
+
bridge.setServerBasePath(path: path)
|
|
808
830
|
// Notify listeners
|
|
809
831
|
notifyReloadedListeners()
|
|
810
832
|
}
|
|
@@ -819,12 +841,16 @@ import CommonCrypto
|
|
|
819
841
|
}
|
|
820
842
|
|
|
821
843
|
private func notifyNextBundleSetListeners(_ bundleId: String?) {
|
|
822
|
-
let
|
|
823
|
-
|
|
844
|
+
if let eventEmitter = eventEmitter {
|
|
845
|
+
let event = NextBundleSetEvent(bundleId: bundleId)
|
|
846
|
+
eventEmitter.onNextBundleSet(event)
|
|
847
|
+
}
|
|
824
848
|
}
|
|
825
849
|
|
|
826
850
|
private func notifyReloadedListeners() {
|
|
827
|
-
|
|
851
|
+
if let eventEmitter = eventEmitter {
|
|
852
|
+
eventEmitter.onReloaded()
|
|
853
|
+
}
|
|
828
854
|
}
|
|
829
855
|
|
|
830
856
|
private func addBlockedBundleId(_ bundleId: String) {
|
|
@@ -852,7 +878,7 @@ import CommonCrypto
|
|
|
852
878
|
let newBlockedIds = blockedList.joined(separator: ",")
|
|
853
879
|
preferences.setBlockedBundleIds(newBlockedIds)
|
|
854
880
|
|
|
855
|
-
CAPLog.print("[",
|
|
881
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Bundle blocked: ", bundleId)
|
|
856
882
|
}
|
|
857
883
|
|
|
858
884
|
private func isBlockedBundleId(_ bundleId: String) -> Bool {
|
|
@@ -882,7 +908,7 @@ import CommonCrypto
|
|
|
882
908
|
|
|
883
909
|
if lastVersionCode == nil || lastVersionName == nil || lastVersionCode != currentVersionCode || lastVersionName != currentVersionName {
|
|
884
910
|
CAPLog.print(
|
|
885
|
-
"[",
|
|
911
|
+
"[", LiveUpdate.tag, "] ",
|
|
886
912
|
"App version changed (last: \(lastVersionName ?? "nil")/\(lastVersionCode ?? "nil"), current: \(currentVersionName)/\(currentVersionCode)), resetting config."
|
|
887
913
|
)
|
|
888
914
|
resetConfig()
|
|
@@ -923,9 +949,10 @@ import CommonCrypto
|
|
|
923
949
|
}
|
|
924
950
|
}
|
|
925
951
|
|
|
926
|
-
private func unzipFile(zipFile: URL) -> URL {
|
|
952
|
+
private func unzipFile(zipFile: URL) throws -> URL {
|
|
927
953
|
let destinationDirectory = zipFile.deletingPathExtension()
|
|
928
|
-
|
|
954
|
+
try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
955
|
+
try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
|
|
929
956
|
return destinationDirectory
|
|
930
957
|
}
|
|
931
958
|
|
|
@@ -967,7 +994,7 @@ import CommonCrypto
|
|
|
967
994
|
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
968
995
|
.replacingOccurrences(of: "\n", with: "")
|
|
969
996
|
guard let publicKeyData = Data(base64Encoded: publicKeyAsBase64) else {
|
|
970
|
-
CAPLog.print("[",
|
|
997
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to decode public key.")
|
|
971
998
|
return false
|
|
972
999
|
}
|
|
973
1000
|
let publicKeyAttributes: [CFString: Any] = [
|
|
@@ -979,12 +1006,12 @@ import CommonCrypto
|
|
|
979
1006
|
var secKeyCreateWithDataError: Unmanaged<CFError>?
|
|
980
1007
|
guard let publicKey = SecKeyCreateWithData(publicKeyData as CFData, publicKeyAttributes as CFDictionary, &secKeyCreateWithDataError) else {
|
|
981
1008
|
if let error = secKeyCreateWithDataError?.takeRetainedValue() {
|
|
982
|
-
CAPLog.print("[",
|
|
1009
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to create public key with error: \(error)")
|
|
983
1010
|
}
|
|
984
1011
|
return false
|
|
985
1012
|
}
|
|
986
1013
|
guard let signatureData = Data(base64Encoded: signature) else {
|
|
987
|
-
CAPLog.print("[",
|
|
1014
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to decode signature.")
|
|
988
1015
|
return false
|
|
989
1016
|
}
|
|
990
1017
|
|
|
@@ -1014,7 +1041,7 @@ import CommonCrypto
|
|
|
1014
1041
|
let signatureAlgorithm = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
|
|
1015
1042
|
let verificationResult = SecKeyVerifySignature(publicKey, signatureAlgorithm, digest as CFData, signatureData as CFData, &secKeyVerifySignatureError)
|
|
1016
1043
|
if let error = secKeyVerifySignatureError?.takeRetainedValue() {
|
|
1017
|
-
CAPLog.print("[",
|
|
1044
|
+
CAPLog.print("[", LiveUpdate.tag, "] ", "Failed to verify signature with error: \(error)")
|
|
1018
1045
|
}
|
|
1019
1046
|
return verificationResult
|
|
1020
1047
|
}
|
|
@@ -7,9 +7,8 @@ import Capacitor
|
|
|
7
7
|
*/
|
|
8
8
|
@objc(LiveUpdatePlugin)
|
|
9
9
|
public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
10
|
-
public static let tag =
|
|
11
|
-
public static let version =
|
|
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,14 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
49
48
|
private var implementation: LiveUpdate?
|
|
50
49
|
|
|
51
50
|
override public func load() {
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
guard let bridge = bridge else {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
let config = liveUpdateConfig()
|
|
55
|
+
self.config = config
|
|
56
|
+
let bridgeWrapper = PluginLiveUpdateBridge(bridge: bridge)
|
|
57
|
+
let eventEmitter = PluginLiveUpdateEventEmitter(plugin: self)
|
|
58
|
+
self.implementation = LiveUpdate.getInstance(bridge: bridgeWrapper, config: config, eventEmitter: eventEmitter)
|
|
54
59
|
|
|
55
60
|
// Notify implementation about load
|
|
56
61
|
implementation?.handleLoad()
|
|
@@ -422,4 +427,46 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
422
427
|
private func resolveCall(_ call: CAPPluginCall, _ result: JSObject) {
|
|
423
428
|
call.resolve(result)
|
|
424
429
|
}
|
|
430
|
+
|
|
431
|
+
private class PluginLiveUpdateBridge: NSObject, LiveUpdateBridge {
|
|
432
|
+
private weak var bridge: CAPBridgeProtocol?
|
|
433
|
+
|
|
434
|
+
init(bridge: CAPBridgeProtocol) {
|
|
435
|
+
self.bridge = bridge
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
func getServerBasePath() -> String? {
|
|
439
|
+
guard let viewController = bridge?.viewController as? CAPBridgeViewController else {
|
|
440
|
+
return nil
|
|
441
|
+
}
|
|
442
|
+
return viewController.getServerBasePath()
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
func setServerBasePath(path: String) {
|
|
446
|
+
guard let viewController = bridge?.viewController as? CAPBridgeViewController else {
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
viewController.setServerBasePath(path: path)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private class PluginLiveUpdateEventEmitter: NSObject, LiveUpdateEventEmitter {
|
|
454
|
+
weak var plugin: LiveUpdatePlugin?
|
|
455
|
+
|
|
456
|
+
init(plugin: LiveUpdatePlugin) {
|
|
457
|
+
self.plugin = plugin
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
func onDownloadBundleProgress(_ event: DownloadBundleProgressEvent) {
|
|
461
|
+
plugin?.notifyDownloadBundleProgressListeners(event)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
func onNextBundleSet(_ event: NextBundleSetEvent) {
|
|
465
|
+
plugin?.notifyNextBundleSetListeners(event)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
func onReloaded() {
|
|
469
|
+
plugin?.notifyReloadedListeners()
|
|
470
|
+
}
|
|
471
|
+
}
|
|
425
472
|
}
|
|
@@ -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
|
|
102
|
+
return LiveUpdatePreferences.userDefaultsPrefix + "." + key
|
|
101
103
|
}
|
|
102
104
|
}
|
package/package.json
CHANGED