@capawesome/capacitor-live-update 8.2.1-dev.70823ec8.1778138208 → 8.2.2

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.
@@ -17,19 +17,4 @@ Pod::Spec.new do |s|
17
17
  s.dependency 'ZIPFoundation', '~> 0.9.0'
18
18
  s.swift_version = '5.1'
19
19
  s.static_framework = true
20
- s.default_subspec = 'Default'
21
-
22
- s.subspec 'Default' do |ss|
23
- # Default subspec
24
- end
25
-
26
- # Opt-in subspec that pulls in the Ionic Live Update Provider SDK and
27
- # compiles in the provider/manager classes guarded by
28
- # `#if CAPAWESOME_INCLUDE_IONIC_PROVIDER`.
29
- s.subspec 'IonicProvider' do |ss|
30
- ss.dependency 'LiveUpdateProvider', '0.1.0-alpha.2'
31
- ss.pod_target_xcconfig = {
32
- 'OTHER_SWIFT_FLAGS' => '$(inherited) -DCAPAWESOME_INCLUDE_IONIC_PROVIDER'
33
- }
34
- end
35
20
  end
package/Package.swift CHANGED
@@ -1,4 +1,4 @@
1
- // swift-tools-version: 6.1
1
+ // swift-tools-version: 5.9
2
2
  import PackageDescription
3
3
 
4
4
  let package = Package(
@@ -9,17 +9,10 @@ let package = Package(
9
9
  name: "CapawesomeCapacitorLiveUpdate",
10
10
  targets: ["LiveUpdatePlugin"])
11
11
  ],
12
- traits: [
13
- .trait(
14
- name: "IonicProvider",
15
- description: "Enables registration with the Ionic Live Update Provider SDK."
16
- )
17
- ],
18
12
  dependencies: [
19
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
20
14
  .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMinor(from: "5.10.2")),
21
- .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMinor(from: "0.9.0")),
22
- .package(url: "https://github.com/ionic-team/live-update-provider-sdk.git", exact: "0.1.0-alpha.2")
15
+ .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMinor(from: "0.9.0"))
23
16
  ],
24
17
  targets: [
25
18
  .target(
@@ -28,21 +21,12 @@ let package = Package(
28
21
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
29
22
  .product(name: "Cordova", package: "capacitor-swift-pm"),
30
23
  .product(name: "Alamofire", package: "Alamofire"),
31
- .product(name: "ZIPFoundation", package: "ZIPFoundation"),
32
- .product(
33
- name: "LiveUpdateProvider",
34
- package: "live-update-provider-sdk",
35
- condition: .when(traits: ["IonicProvider"])
36
- )
24
+ .product(name: "ZIPFoundation", package: "ZIPFoundation")
37
25
  ],
38
- path: "ios/Plugin",
39
- swiftSettings: [
40
- .define("CAPAWESOME_INCLUDE_IONIC_PROVIDER", .when(traits: ["IonicProvider"]))
41
- ]),
26
+ path: "ios/Plugin"),
42
27
  .testTarget(
43
28
  name: "LiveUpdatePluginTests",
44
29
  dependencies: ["LiveUpdatePlugin"],
45
30
  path: "ios/PluginTests")
46
- ],
47
- swiftLanguageModes: [.v5]
31
+ ]
48
32
  )
package/README.md CHANGED
@@ -25,8 +25,6 @@ We are proud to offer one of the most complete and feature-rich Capacitor plugin
25
25
  - 📡 **Update Lifecycle Events**: Track download progress, react to bundle changes, and detect reloads with auto-blocking of rolled back bundles.
26
26
  - 🏠 **Self-Hosted Bundles**: Download bundles from any URL, no Capawesome Cloud dependency required.
27
27
  - 🏷️ **Custom Properties**: Associate custom key-value metadata with bundles via Capawesome Cloud.
28
- - 🌀 **Ionic Portals Support**: Power live updates for [Ionic Portals](https://ionic.io/docs/portals) via the [Ionic Live Update Provider SDK](https://github.com/ionic-team/live-update-provider-sdk).
29
- - 🧩 **Federated Capacitor Support**: Power live updates for [Federated Capacitor](https://ionic.io/docs/portals/for-capacitor/overview) shells via the [Ionic Live Update Provider SDK](https://github.com/ionic-team/live-update-provider-sdk).
30
28
  - 🔒 **Security**: Verify the authenticity and integrity of the bundle using a public key.
31
29
  - ⚔️ **Battle-Tested**: Used in more than 1,000 projects to update apps on more than 20,000,000 devices.
32
30
  - 🌐 **Open Source**: Licensed under the MIT License.
@@ -42,8 +40,8 @@ Stay up to date with the latest news and updates about the Capawesome, Capacitor
42
40
  | Plugin Version | Capacitor Version | Status |
43
41
  | -------------- | ----------------- | -------------- |
44
42
  | 8.x.x | >=8.x.x | Active support |
45
- | 7.x.x | 7.x.x | Maintenance |
46
- | 6.x.x | 6.x.x | Maintenance |
43
+ | 7.x.x | 7.x.x | Deprecated |
44
+ | 6.x.x | 6.x.x | Deprecated |
47
45
  | 5.x.x | 5.x.x | Deprecated |
48
46
 
49
47
  ## Guides
@@ -141,10 +139,6 @@ Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Priva
141
139
 
142
140
  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.
143
141
 
144
- ## Ionic Live Update Provider SDK integration
145
-
146
- This plugin can register itself as a provider for the [Ionic Live Update Provider SDK](https://github.com/ionic-team/live-update-provider-sdk), so that **Ionic Portals** and **Federated Capacitor** apps can use Capawesome Cloud to deliver live updates. See [the full setup guide](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/docs/ionic-live-update-provider-sdk-integration.md) for details.
147
-
148
142
  ## Configuration
149
143
 
150
144
  <docgen-config>
@@ -5,8 +5,6 @@ ext {
5
5
  androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
6
  okhttp3Version = project.hasProperty('okhttp3Version') ? rootProject.ext.okhttp3Version : '5.3.2'
7
7
  zip4jVersion = project.hasProperty('zip4jVersion') ? rootProject.ext.zip4jVersion : '2.11.5'
8
- capawesomeCapacitorLiveUpdateIncludeIonicProvider = project.hasProperty('capawesomeCapacitorLiveUpdateIncludeIonicProvider') ? rootProject.ext.capawesomeCapacitorLiveUpdateIncludeIonicProvider : false
9
- ionicLiveUpdateProviderVersion = project.hasProperty('ionicLiveUpdateProviderVersion') ? rootProject.ext.ionicLiveUpdateProviderVersion : '0.1.0-alpha.2'
10
8
  }
11
9
 
12
10
  buildscript {
@@ -58,11 +56,6 @@ dependencies {
58
56
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
59
57
  implementation "net.lingala.zip4j:zip4j:$zip4jVersion"
60
58
  implementation "com.squareup.okhttp3:okhttp:$okhttp3Version"
61
- if (capawesomeCapacitorLiveUpdateIncludeIonicProvider) {
62
- implementation "io.ionic:liveupdateprovider:$ionicLiveUpdateProviderVersion"
63
- } else {
64
- compileOnly "io.ionic:liveupdateprovider:$ionicLiveUpdateProviderVersion"
65
- }
66
59
  testImplementation "junit:junit:$junitVersion"
67
60
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
68
61
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -298,17 +298,6 @@ public class LiveUpdate {
298
298
  callback.success(result);
299
299
  }
300
300
 
301
- /**
302
- * @return The on-disk directory of a downloaded bundle, or `null` if the bundle does not exist.
303
- */
304
- @Nullable
305
- public File getBundleDirectory(@NonNull String bundleId) {
306
- if (!hasBundleById(bundleId)) {
307
- return null;
308
- }
309
- return buildBundleDirectoryFor(bundleId);
310
- }
311
-
312
301
  public void getCustomId(@NonNull NonEmptyCallback callback) {
313
302
  String customId = preferences.getCustomId();
314
303
  GetCustomIdResult result = new GetCustomIdResult(customId);
@@ -678,8 +667,11 @@ public class LiveUpdate {
678
667
 
679
668
  private void deleteFileRecursively(@NonNull File file) {
680
669
  if (file.isDirectory()) {
681
- for (File child : file.listFiles()) {
682
- deleteFileRecursively(child);
670
+ File[] children = file.listFiles();
671
+ if (children != null) {
672
+ for (File child : children) {
673
+ deleteFileRecursively(child);
674
+ }
683
675
  }
684
676
  }
685
677
  file.delete();
@@ -983,17 +975,13 @@ public class LiveUpdate {
983
975
  @NonNull NonEmptyCallback<GetLatestBundleResponse> callback
984
976
  ) {
985
977
  try {
986
- String appId = options.getAppId() == null ? getAppId() : options.getAppId();
987
- if (appId == null || appId.isEmpty()) {
988
- throw new Exception(LiveUpdatePlugin.ERROR_APP_ID_MISSING);
989
- }
990
978
  String channel = options.getChannel() == null ? getChannel() : options.getChannel();
991
979
  String url = new HttpUrl.Builder()
992
980
  .scheme("https")
993
981
  .host(config.getServerDomain())
994
982
  .addPathSegment("v1")
995
983
  .addPathSegment("apps")
996
- .addPathSegment(appId)
984
+ .addPathSegment(getAppId())
997
985
  .addPathSegment("bundles")
998
986
  .addPathSegment("latest")
999
987
  .addQueryParameter("appVersionCode", getVersionCodeAsString())
@@ -32,7 +32,6 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.IsSyncingRes
32
32
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
33
33
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
34
34
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
35
- import io.capawesome.capacitorjs.plugins.liveupdate.providers.ionic.LiveUpdateIonicProviderRegistration;
36
35
 
37
36
  @CapacitorPlugin(name = "LiveUpdate")
38
37
  public class LiveUpdatePlugin extends Plugin {
@@ -41,7 +40,6 @@ public class LiveUpdatePlugin extends Plugin {
41
40
  public static final String VERSION = "8.2.0";
42
41
  public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
43
42
  public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
44
- public static final String ERROR_BUNDLE_DIRECTORY_NOT_FOUND = "Bundle directory could not be resolved.";
45
43
  public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
46
44
  public static final String ERROR_BUNDLE_ID_MISSING = "bundleId must be provided.";
47
45
  public static final String ERROR_BUNDLE_INDEX_HTML_MISSING = "The bundle does not contain an index.html file.";
@@ -50,9 +48,7 @@ public class LiveUpdatePlugin extends Plugin {
50
48
  public static final String ERROR_CHECKSUM_MISMATCH = "Checksum mismatch.";
51
49
  public static final String ERROR_CUSTOM_ID_MISSING = "customId must be provided.";
52
50
  public static final String ERROR_DOWNLOAD_FAILED = "Bundle could not be downloaded.";
53
- public static final String ERROR_DOWNLOAD_URL_MISSING = "Bundle does not have a valid download URL.";
54
51
  public static final String ERROR_HTTP_TIMEOUT = "Request timed out.";
55
- public static final String ERROR_MANAGER_KEY_MISSING = "managerKey must be provided.";
56
52
  public static final String ERROR_URL_MISSING = "url must be provided.";
57
53
  public static final String ERROR_SIGNATURE_VERIFICATION_FAILED = "Signature verification failed.";
58
54
  public static final String ERROR_PUBLIC_KEY_INVALID = "Invalid public key.";
@@ -75,10 +71,6 @@ public class LiveUpdatePlugin extends Plugin {
75
71
  try {
76
72
  config = getLiveUpdateConfig();
77
73
  implementation = new LiveUpdate(config, this);
78
- // Register the Ionic Live Update Provider when the optional SDK is on the classpath.
79
- if (LiveUpdateIonicProviderRegistration.isAvailable()) {
80
- LiveUpdateIonicProviderRegistration.tryRegister(implementation);
81
- }
82
74
  } catch (Exception exception) {
83
75
  Logger.error(TAG, exception.getMessage(), exception);
84
76
  }
@@ -6,32 +6,17 @@ import com.getcapacitor.PluginCall;
6
6
 
7
7
  public class FetchLatestBundleOptions {
8
8
 
9
- @Nullable
10
- private final String appId;
11
-
12
9
  @Nullable
13
10
  private final String channel;
14
11
 
15
12
  public FetchLatestBundleOptions(@NonNull PluginCall call) {
16
- this.appId = null;
17
13
  this.channel = call.getString("channel", null);
18
14
  }
19
15
 
20
16
  public FetchLatestBundleOptions(@Nullable String channel) {
21
- this.appId = null;
22
- this.channel = channel;
23
- }
24
-
25
- public FetchLatestBundleOptions(@Nullable String appId, @Nullable String channel) {
26
- this.appId = appId;
27
17
  this.channel = channel;
28
18
  }
29
19
 
30
- @Nullable
31
- public String getAppId() {
32
- return appId;
33
- }
34
-
35
20
  @Nullable
36
21
  public String getChannel() {
37
22
  return channel;
@@ -43,36 +43,6 @@ public class FetchLatestBundleResult implements Result {
43
43
  this.signature = signature;
44
44
  }
45
45
 
46
- @Nullable
47
- public ArtifactType getArtifactType() {
48
- return artifactType;
49
- }
50
-
51
- @Nullable
52
- public String getBundleId() {
53
- return bundleId;
54
- }
55
-
56
- @Nullable
57
- public String getChecksum() {
58
- return checksum;
59
- }
60
-
61
- @Nullable
62
- public JSONObject getCustomProperties() {
63
- return customProperties;
64
- }
65
-
66
- @Nullable
67
- public String getDownloadUrl() {
68
- return downloadUrl;
69
- }
70
-
71
- @Nullable
72
- public String getSignature() {
73
- return signature;
74
- }
75
-
76
46
  @NonNull
77
47
  public JSObject toJSObject() {
78
48
  JSObject result = new JSObject();
@@ -2,28 +2,16 @@ import Foundation
2
2
  import Capacitor
3
3
 
4
4
  @objc public class FetchLatestBundleOptions: NSObject {
5
- private var appId: String?
6
5
  private var channel: String?
7
6
 
8
7
  init(_ call: CAPPluginCall) {
9
- self.appId = nil
10
8
  self.channel = call.getString("channel")
11
9
  }
12
10
 
13
11
  init(channel: String?) {
14
- self.appId = nil
15
12
  self.channel = channel
16
13
  }
17
14
 
18
- init(appId: String?, channel: String?) {
19
- self.appId = appId
20
- self.channel = channel
21
- }
22
-
23
- func getAppId() -> String? {
24
- return appId
25
- }
26
-
27
15
  func getChannel() -> String? {
28
16
  return channel
29
17
  }
@@ -18,30 +18,6 @@ import Capacitor
18
18
  self.signature = signature
19
19
  }
20
20
 
21
- public func getArtifactType() -> String? {
22
- return artifactType?.rawValue
23
- }
24
-
25
- public func getBundleId() -> String? {
26
- return bundleId
27
- }
28
-
29
- public func getChecksum() -> String? {
30
- return checksum
31
- }
32
-
33
- public func getCustomProperties() -> [String: Any]? {
34
- return customProperties
35
- }
36
-
37
- public func getDownloadUrl() -> String? {
38
- return downloadUrl
39
- }
40
-
41
- public func getSignature() -> String? {
42
- return signature
43
- }
44
-
45
21
  public func toJSObject() -> AnyObject {
46
22
  var result = JSObject()
47
23
  if artifactType == .manifest {
@@ -3,7 +3,6 @@ import Foundation
3
3
  public enum CustomError: Error {
4
4
  case appIdMissing
5
5
  case bundleAlreadyExists
6
- case bundleDirectoryNotFound
7
6
  case bundleIdMissing
8
7
  case bundleIndexHtmlMissing
9
8
  case bundleNotFound
@@ -13,9 +12,7 @@ public enum CustomError: Error {
13
12
  case checksumMismatch
14
13
  case customIdMissing
15
14
  case downloadFailed
16
- case downloadUrlMissing
17
15
  case httpTimeout
18
- case managerKeyMissing
19
16
  case signatureMissing
20
17
  case signatureVerificationFailed
21
18
  case syncInProgress
@@ -30,8 +27,6 @@ extension CustomError: LocalizedError {
30
27
  return NSLocalizedString("appId must be configured.", comment: "appIdMissing")
31
28
  case .bundleAlreadyExists:
32
29
  return NSLocalizedString("bundle already exists.", comment: "bundleAlreadyExists")
33
- case .bundleDirectoryNotFound:
34
- return NSLocalizedString("Bundle directory could not be resolved.", comment: "bundleDirectoryNotFound")
35
30
  case .bundleIdMissing:
36
31
  return NSLocalizedString("bundleId must be provided.", comment: "bundleIdMissing")
37
32
  case .bundleIndexHtmlMissing:
@@ -50,12 +45,8 @@ extension CustomError: LocalizedError {
50
45
  return NSLocalizedString("customId must be provided.", comment: "customIdMissing")
51
46
  case .downloadFailed:
52
47
  return NSLocalizedString("Bundle could not be downloaded.", comment: "downloadFailed")
53
- case .downloadUrlMissing:
54
- return NSLocalizedString("Bundle does not have a valid download URL.", comment: "downloadUrlMissing")
55
48
  case .httpTimeout:
56
49
  return NSLocalizedString("Request timed out.", comment: "httpTimeout")
57
- case .managerKeyMissing:
58
- return NSLocalizedString("managerKey must be provided.", comment: "managerKeyMissing")
59
50
  case .signatureMissing:
60
51
  return NSLocalizedString("Bundle does not contain a signature.", comment: "signatureMissing")
61
52
  case .signatureVerificationFailed:
@@ -161,14 +161,6 @@ import CommonCrypto
161
161
  completion(result, nil)
162
162
  }
163
163
 
164
- /// - Returns: The on-disk URL of a downloaded bundle, or `nil` if the bundle does not exist.
165
- public func getBundleDirectory(bundleId: String) -> URL? {
166
- guard hasBundleById(bundleId) else {
167
- return nil
168
- }
169
- return buildBundleURLFor(bundleId: bundleId)
170
- }
171
-
172
164
  @objc public func getCustomId(completion: @escaping (Result?, Error?) -> Void) {
173
165
  let customId = preferences.getCustomId()
174
166
 
@@ -588,9 +580,6 @@ import CommonCrypto
588
580
  }
589
581
 
590
582
  private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
591
- guard let appId = options.getAppId() ?? getAppId(), !appId.isEmpty else {
592
- throw CustomError.appIdMissing
593
- }
594
583
  let channel = options.getChannel() ?? getChannel()
595
584
  var parameters = [String: String]()
596
585
  parameters["appVersionCode"] = getVersionCode()
@@ -602,7 +591,7 @@ import CommonCrypto
602
591
  parameters["osVersion"] = await UIDevice.current.systemVersion
603
592
  parameters["platform"] = "1"
604
593
  parameters["pluginVersion"] = LiveUpdatePlugin.version
605
- var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(appId)/bundles/latest")!
594
+ var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/bundles/latest")!
606
595
  urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
607
596
  let url = try urlComponents.asURL()
608
597
  CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle: ", url)
@@ -1,8 +1,5 @@
1
1
  import Foundation
2
2
  import Capacitor
3
- #if CAPAWESOME_INCLUDE_IONIC_PROVIDER
4
- import LiveUpdateProvider
5
- #endif
6
3
 
7
4
  /**
8
5
  * Please read the Capacitor iOS Plugin Development Guide
@@ -59,22 +56,6 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
59
56
  // Notify implementation about load
60
57
  implementation?.handleLoad()
61
58
 
62
- // Register the Ionic Live Update Provider when the optional SDK is linked.
63
- #if CAPAWESOME_INCLUDE_IONIC_PROVIDER
64
- if let implementation = implementation {
65
- do {
66
- if LiveUpdateProviderRegistry.shared.resolve(LiveUpdateIonicProvider.id) == nil {
67
- try LiveUpdateProviderRegistry.shared.register(
68
- LiveUpdateIonicProvider(liveUpdate: implementation)
69
- )
70
- CAPLog.print("[", LiveUpdatePlugin.tag, "] Registered Ionic provider '\(LiveUpdateIonicProvider.id)'.")
71
- }
72
- } catch {
73
- CAPLog.print("[", LiveUpdatePlugin.tag, "] Failed to register Ionic provider: ", error.localizedDescription)
74
- }
75
- }
76
- #endif
77
-
78
59
  // Register for app resume notifications
79
60
  NotificationCenter.default.addObserver(
80
61
  self,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "8.2.1-dev.70823ec8.1778138208",
3
+ "version": "8.2.2",
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",
@@ -1,232 +0,0 @@
1
- package io.capawesome.capacitorjs.plugins.liveupdate.providers.ionic;
2
-
3
- import android.content.Context;
4
- import android.content.SharedPreferences;
5
- import androidx.annotation.NonNull;
6
- import androidx.annotation.Nullable;
7
- import com.getcapacitor.Logger;
8
- import io.capawesome.capacitorjs.plugins.liveupdate.LiveUpdate;
9
- import io.capawesome.capacitorjs.plugins.liveupdate.LiveUpdatePlugin;
10
- import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
11
- import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
12
- import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
13
- import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
14
- import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
15
- import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
16
- import io.ionic.liveupdateprovider.FederatedCapacitorSyncResult;
17
- import io.ionic.liveupdateprovider.LiveUpdateProviderError;
18
- import io.ionic.liveupdateprovider.LiveUpdateProviderManager;
19
- import io.ionic.liveupdateprovider.LiveUpdateProviderSyncCallback;
20
- import java.io.File;
21
- import java.util.HashMap;
22
- import java.util.Iterator;
23
- import java.util.Map;
24
- import org.json.JSONObject;
25
-
26
- /**
27
- * Ionic Live Update Provider manager backed by the Capawesome live update plugin.
28
- *
29
- * Each manager instance is scoped by a {@code managerKey} so that multiple shells (Portals or
30
- * Federated Capacitor apps) can persist their own active bundle without colliding with each
31
- * other or with the standalone plugin's {@code currentBundleId} / {@code nextBundleId} state.
32
- *
33
- * Provider config keys (V1):
34
- * <ul>
35
- * <li>{@code managerKey} (required) — scopes per-manager persisted state</li>
36
- * <li>{@code appId} (optional) — Capawesome Cloud app UUID; falls back to plugin config</li>
37
- * <li>{@code channel} (optional) — channel to sync; falls back to plugin config</li>
38
- * </ul>
39
- */
40
- public class LiveUpdateIonicManager implements LiveUpdateProviderManager {
41
-
42
- public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdateIonicProvider";
43
- public static final String PREF_PREFIX_LAST_SYNCED_BUNDLE_ID = "lastSyncedBundleId.";
44
-
45
- @Nullable
46
- private final String appId;
47
-
48
- @Nullable
49
- private final String channel;
50
-
51
- @Nullable
52
- private File latestAppDirectory;
53
-
54
- @NonNull
55
- private final LiveUpdate liveUpdate;
56
-
57
- @NonNull
58
- private final String managerKey;
59
-
60
- @NonNull
61
- private final SharedPreferences sharedPreferences;
62
-
63
- public LiveUpdateIonicManager(@NonNull Context context, @NonNull Map<String, ?> config, @NonNull LiveUpdate liveUpdate)
64
- throws LiveUpdateProviderError.InvalidConfiguration {
65
- Object managerKeyValue = config.get("managerKey");
66
- if (!(managerKeyValue instanceof String) || ((String) managerKeyValue).isEmpty()) {
67
- throw new LiveUpdateProviderError.InvalidConfiguration(LiveUpdatePlugin.ERROR_MANAGER_KEY_MISSING, null);
68
- }
69
- this.managerKey = (String) managerKeyValue;
70
-
71
- Object appIdValue = config.get("appId");
72
- this.appId = appIdValue instanceof String ? (String) appIdValue : null;
73
-
74
- Object channelValue = config.get("channel");
75
- this.channel = channelValue instanceof String ? (String) channelValue : null;
76
-
77
- this.liveUpdate = liveUpdate;
78
- this.sharedPreferences = context.getApplicationContext().getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
79
-
80
- // Restore the last synced bundle directory, if any.
81
- String persistedBundleId = sharedPreferences.getString(getLastSyncedBundleIdKey(), null);
82
- if (persistedBundleId != null) {
83
- this.latestAppDirectory = liveUpdate.getBundleDirectory(persistedBundleId);
84
- }
85
- }
86
-
87
- @Nullable
88
- @Override
89
- public File getLatestAppDirectory() {
90
- return latestAppDirectory;
91
- }
92
-
93
- @Override
94
- public void sync(@Nullable LiveUpdateProviderSyncCallback callback) {
95
- try {
96
- FetchLatestBundleOptions fetchOptions = new FetchLatestBundleOptions(appId, channel);
97
- liveUpdate.fetchLatestBundle(
98
- fetchOptions,
99
- new NonEmptyCallback<FetchLatestBundleResult>() {
100
- @Override
101
- public void success(@NonNull FetchLatestBundleResult result) {
102
- handleFetchLatestBundleSuccess(result, callback);
103
- }
104
-
105
- @Override
106
- public void error(@NonNull Exception exception) {
107
- notifyFailure(callback, exception);
108
- }
109
- }
110
- );
111
- } catch (Exception exception) {
112
- notifyFailure(callback, exception);
113
- }
114
- }
115
-
116
- private void handleFetchLatestBundleSuccess(
117
- @NonNull FetchLatestBundleResult result,
118
- @Nullable LiveUpdateProviderSyncCallback callback
119
- ) {
120
- try {
121
- String bundleId = result.getBundleId();
122
- if (bundleId == null) {
123
- // No update available; report success without changing state.
124
- notifySuccess(callback, null, null);
125
- return;
126
- }
127
-
128
- // Bundle already on disk — just point latestAppDirectory at it and persist.
129
- File existingDirectory = liveUpdate.getBundleDirectory(bundleId);
130
- if (existingDirectory != null) {
131
- applySyncedBundle(bundleId, existingDirectory);
132
- notifySuccess(callback, bundleId, result.getCustomProperties());
133
- return;
134
- }
135
-
136
- // Otherwise, download and then apply.
137
- String downloadUrl = result.getDownloadUrl();
138
- if (downloadUrl == null || downloadUrl.isEmpty()) {
139
- notifyFailure(callback, new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_URL_MISSING));
140
- return;
141
- }
142
- ArtifactType artifactType = result.getArtifactType() == null ? ArtifactType.ZIP : result.getArtifactType();
143
- String artifactTypeString = artifactType == ArtifactType.MANIFEST ? "manifest" : "zip";
144
- DownloadBundleOptions downloadOptions = new DownloadBundleOptions(
145
- artifactTypeString,
146
- bundleId,
147
- result.getChecksum(),
148
- result.getSignature(),
149
- downloadUrl
150
- );
151
- liveUpdate.downloadBundle(
152
- downloadOptions,
153
- new EmptyCallback() {
154
- @Override
155
- public void success() {
156
- File downloadedDirectory = liveUpdate.getBundleDirectory(bundleId);
157
- if (downloadedDirectory == null) {
158
- notifyFailure(callback, new Exception(LiveUpdatePlugin.ERROR_BUNDLE_DIRECTORY_NOT_FOUND));
159
- return;
160
- }
161
- applySyncedBundle(bundleId, downloadedDirectory);
162
- notifySuccess(callback, bundleId, result.getCustomProperties());
163
- }
164
-
165
- @Override
166
- public void error(@NonNull Exception exception) {
167
- notifyFailure(callback, exception);
168
- }
169
- }
170
- );
171
- } catch (Exception exception) {
172
- notifyFailure(callback, exception);
173
- }
174
- }
175
-
176
- private void applySyncedBundle(@NonNull String bundleId, @NonNull File directory) {
177
- latestAppDirectory = directory;
178
- sharedPreferences.edit().putString(getLastSyncedBundleIdKey(), bundleId).apply();
179
- }
180
-
181
- private void notifySuccess(
182
- @Nullable LiveUpdateProviderSyncCallback callback,
183
- @Nullable String bundleId,
184
- @Nullable JSONObject customProperties
185
- ) {
186
- if (callback == null) {
187
- return;
188
- }
189
- Map<String, Object> metadata = new HashMap<>();
190
- if (bundleId != null) {
191
- metadata.put("bundleId", bundleId);
192
- }
193
- if (channel != null) {
194
- metadata.put("channel", channel);
195
- }
196
- if (customProperties != null) {
197
- Map<String, Object> customPropertiesMap = jsonObjectToMap(customProperties);
198
- if (!customPropertiesMap.isEmpty()) {
199
- metadata.put("customProperties", customPropertiesMap);
200
- }
201
- }
202
- callback.onSuccess(new FederatedCapacitorSyncResult(metadata));
203
- }
204
-
205
- private void notifyFailure(@Nullable LiveUpdateProviderSyncCallback callback, @NonNull Exception exception) {
206
- Logger.error(LiveUpdatePlugin.TAG, "Ionic provider sync failed: " + exception.getMessage(), exception);
207
- if (callback == null) {
208
- return;
209
- }
210
- String message = exception.getMessage() == null ? LiveUpdatePlugin.ERROR_UNKNOWN_ERROR : exception.getMessage();
211
- callback.onFailure(new LiveUpdateProviderError.SyncFailed(message, exception));
212
- }
213
-
214
- @NonNull
215
- private String getLastSyncedBundleIdKey() {
216
- return PREF_PREFIX_LAST_SYNCED_BUNDLE_ID + managerKey;
217
- }
218
-
219
- @NonNull
220
- private static Map<String, Object> jsonObjectToMap(@NonNull JSONObject jsonObject) {
221
- Map<String, Object> map = new HashMap<>();
222
- Iterator<String> keys = jsonObject.keys();
223
- while (keys.hasNext()) {
224
- String key = keys.next();
225
- Object value = jsonObject.opt(key);
226
- if (value != null && value != JSONObject.NULL) {
227
- map.put(key, value);
228
- }
229
- }
230
- return map;
231
- }
232
- }
@@ -1,41 +0,0 @@
1
- package io.capawesome.capacitorjs.plugins.liveupdate.providers.ionic;
2
-
3
- import android.content.Context;
4
- import androidx.annotation.NonNull;
5
- import io.capawesome.capacitorjs.plugins.liveupdate.LiveUpdate;
6
- import io.ionic.liveupdateprovider.LiveUpdateProvider;
7
- import io.ionic.liveupdateprovider.LiveUpdateProviderError;
8
- import io.ionic.liveupdateprovider.LiveUpdateProviderManager;
9
- import java.util.Map;
10
-
11
- /**
12
- * Ionic Live Update Provider implementation backed by the Capawesome live update plugin.
13
- *
14
- * Registered with {@link io.ionic.liveupdateprovider.LiveUpdateProviderRegistry} when the
15
- * Capacitor plugin loads, so Federated Capacitor can resolve provider id {@code "capawesome"}
16
- * and create managers from the per-shell configuration.
17
- */
18
- public class LiveUpdateIonicProvider implements LiveUpdateProvider {
19
-
20
- public static final String ID = "capawesome";
21
-
22
- @NonNull
23
- private final LiveUpdate liveUpdate;
24
-
25
- public LiveUpdateIonicProvider(@NonNull LiveUpdate liveUpdate) {
26
- this.liveUpdate = liveUpdate;
27
- }
28
-
29
- @NonNull
30
- @Override
31
- public String getId() {
32
- return ID;
33
- }
34
-
35
- @NonNull
36
- @Override
37
- public LiveUpdateProviderManager createManager(@NonNull Context context, @NonNull Map<String, ?> config)
38
- throws LiveUpdateProviderError.InvalidConfiguration {
39
- return new LiveUpdateIonicManager(context, config, liveUpdate);
40
- }
41
- }
@@ -1,46 +0,0 @@
1
- package io.capawesome.capacitorjs.plugins.liveupdate.providers.ionic;
2
-
3
- import androidx.annotation.NonNull;
4
- import com.getcapacitor.Logger;
5
- import io.capawesome.capacitorjs.plugins.liveupdate.LiveUpdate;
6
- import io.capawesome.capacitorjs.plugins.liveupdate.LiveUpdatePlugin;
7
- import io.ionic.liveupdateprovider.LiveUpdateProviderRegistry;
8
-
9
- /**
10
- * Helper that registers the Capawesome live update provider with the Ionic
11
- * {@link LiveUpdateProviderRegistry}, but only when the optional
12
- * {@code io.ionic:liveupdateprovider} dependency is present at runtime.
13
- *
14
- * The Gradle build wires the SDK as either {@code implementation} (when the consumer opts in
15
- * via {@code capawesomeCapacitorLiveUpdateIncludeIonicProvider = true}) or {@code compileOnly}
16
- * (the default). When opted out, the SDK classes are stripped from the consumer APK and the
17
- * reflection check below short-circuits registration.
18
- */
19
- public final class LiveUpdateIonicProviderRegistration {
20
-
21
- private LiveUpdateIonicProviderRegistration() {
22
- // Static utility — no instances.
23
- }
24
-
25
- public static boolean isAvailable() {
26
- try {
27
- Class.forName("io.ionic.liveupdateprovider.LiveUpdateProviderRegistry");
28
- return true;
29
- } catch (ClassNotFoundException exception) {
30
- return false;
31
- }
32
- }
33
-
34
- public static void tryRegister(@NonNull LiveUpdate liveUpdate) {
35
- try {
36
- if (LiveUpdateProviderRegistry.resolve(LiveUpdateIonicProvider.ID) != null) {
37
- Logger.debug(LiveUpdatePlugin.TAG, "Ionic provider '" + LiveUpdateIonicProvider.ID + "' already registered.");
38
- return;
39
- }
40
- LiveUpdateProviderRegistry.register(new LiveUpdateIonicProvider(liveUpdate));
41
- Logger.debug(LiveUpdatePlugin.TAG, "Registered Ionic provider '" + LiveUpdateIonicProvider.ID + "'.");
42
- } catch (Exception exception) {
43
- Logger.error(LiveUpdatePlugin.TAG, "Failed to register Ionic provider: " + exception.getMessage(), exception);
44
- }
45
- }
46
- }
@@ -1,118 +0,0 @@
1
- #if CAPAWESOME_INCLUDE_IONIC_PROVIDER
2
- import Foundation
3
- import LiveUpdateProvider
4
-
5
- /// Ionic Live Update Provider manager backed by the Capawesome live update plugin.
6
- ///
7
- /// Each manager instance is scoped by a `managerKey` so that multiple shells (Portals or
8
- /// Federated Capacitor apps) can persist their own active bundle without colliding with
9
- /// each other or with the standalone plugin's `currentBundleId` / `nextBundleId` state.
10
- ///
11
- /// Provider config keys (V1):
12
- /// - `managerKey` (required) — scopes per-manager persisted state
13
- /// - `appId` (optional) — Capawesome Cloud app UUID; falls back to plugin config
14
- /// - `channel` (optional) — channel to sync; falls back to plugin config
15
- public final class LiveUpdateIonicManager: LiveUpdateManaging {
16
- private static let userDefaultsPrefix = "CapawesomeLiveUpdateIonicProvider.lastSyncedBundleId."
17
-
18
- private let appId: String?
19
- private let channel: String?
20
- private let liveUpdate: LiveUpdate
21
- private let managerKey: String
22
-
23
- public private(set) var latestAppDirectory: URL?
24
-
25
- public init(config: [String: Any], liveUpdate: LiveUpdate) throws {
26
- guard let managerKey = config["managerKey"] as? String, !managerKey.isEmpty else {
27
- throw LiveUpdateProviderError.invalidConfiguration(
28
- CustomError.managerKeyMissing.localizedDescription,
29
- underlyingError: nil
30
- )
31
- }
32
- self.managerKey = managerKey
33
- self.appId = config["appId"] as? String
34
- self.channel = config["channel"] as? String
35
- self.liveUpdate = liveUpdate
36
-
37
- // Restore the last synced bundle directory, if any.
38
- if let persistedBundleId = UserDefaults.standard.string(forKey: lastSyncedBundleIdKey()) {
39
- self.latestAppDirectory = liveUpdate.getBundleDirectory(bundleId: persistedBundleId)
40
- }
41
- }
42
-
43
- public func sync() async throws -> any LiveUpdateProvider.SyncResult {
44
- do {
45
- let fetchOptions = FetchLatestBundleOptions(appId: appId, channel: channel)
46
- let result = try await liveUpdate.fetchLatestBundle(fetchOptions)
47
-
48
- guard let bundleId = result.getBundleId() else {
49
- // No update available; report success without changing state.
50
- return DefaultFederatedCapacitorSyncResult(metadata: buildMetadata(bundleId: nil, customProperties: nil))
51
- }
52
-
53
- // Bundle already on disk — just point latestAppDirectory at it and persist.
54
- if let existingDirectory = liveUpdate.getBundleDirectory(bundleId: bundleId) {
55
- applySyncedBundle(bundleId: bundleId, directory: existingDirectory)
56
- return DefaultFederatedCapacitorSyncResult(
57
- metadata: buildMetadata(bundleId: bundleId, customProperties: result.getCustomProperties())
58
- )
59
- }
60
-
61
- // Otherwise, download and then apply.
62
- guard let downloadUrl = result.getDownloadUrl(), !downloadUrl.isEmpty else {
63
- throw LiveUpdateProviderError.syncFailed(
64
- CustomError.downloadUrlMissing.localizedDescription,
65
- underlyingError: nil
66
- )
67
- }
68
- let artifactType = result.getArtifactType() ?? "zip"
69
- let downloadOptions = DownloadBundleOptions(
70
- artifactType: artifactType,
71
- bundleId: bundleId,
72
- checksum: result.getChecksum(),
73
- signature: result.getSignature(),
74
- url: downloadUrl
75
- )
76
- try await liveUpdate.downloadBundle(downloadOptions)
77
-
78
- guard let downloadedDirectory = liveUpdate.getBundleDirectory(bundleId: bundleId) else {
79
- throw LiveUpdateProviderError.syncFailed(
80
- CustomError.bundleDirectoryNotFound.localizedDescription,
81
- underlyingError: nil
82
- )
83
- }
84
- applySyncedBundle(bundleId: bundleId, directory: downloadedDirectory)
85
- return DefaultFederatedCapacitorSyncResult(
86
- metadata: buildMetadata(bundleId: bundleId, customProperties: result.getCustomProperties())
87
- )
88
- } catch let error as LiveUpdateProviderError {
89
- throw error
90
- } catch {
91
- throw LiveUpdateProviderError.syncFailed(error.localizedDescription, underlyingError: error)
92
- }
93
- }
94
-
95
- private func applySyncedBundle(bundleId: String, directory: URL) {
96
- latestAppDirectory = directory
97
- UserDefaults.standard.set(bundleId, forKey: lastSyncedBundleIdKey())
98
- }
99
-
100
- private func buildMetadata(bundleId: String?, customProperties: [String: Any]?) -> [String: Any]? {
101
- var metadata: [String: Any] = [:]
102
- if let bundleId = bundleId {
103
- metadata["bundleId"] = bundleId
104
- }
105
- if let channel = channel {
106
- metadata["channel"] = channel
107
- }
108
- if let customProperties = customProperties, !customProperties.isEmpty {
109
- metadata["customProperties"] = customProperties
110
- }
111
- return metadata.isEmpty ? nil : metadata
112
- }
113
-
114
- private func lastSyncedBundleIdKey() -> String {
115
- return Self.userDefaultsPrefix + managerKey
116
- }
117
- }
118
- #endif
@@ -1,25 +0,0 @@
1
- #if CAPAWESOME_INCLUDE_IONIC_PROVIDER
2
- import Foundation
3
- import LiveUpdateProvider
4
-
5
- /// Ionic Live Update Provider implementation backed by the Capawesome live update plugin.
6
- ///
7
- /// Registered with `LiveUpdateProviderRegistry.shared` when the Capacitor plugin loads, so
8
- /// Federated Capacitor can resolve provider id `"capawesome"` and create managers from the
9
- /// per-shell configuration.
10
- public struct LiveUpdateIonicProvider: LiveUpdateProviding {
11
- public static let id = "capawesome"
12
-
13
- public let id: String = LiveUpdateIonicProvider.id
14
-
15
- private let liveUpdate: LiveUpdate
16
-
17
- public init(liveUpdate: LiveUpdate) {
18
- self.liveUpdate = liveUpdate
19
- }
20
-
21
- public func createManager(config: [String: Any]) throws -> any LiveUpdateManaging {
22
- return try LiveUpdateIonicManager(config: config, liveUpdate: liveUpdate)
23
- }
24
- }
25
- #endif