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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +15 -0
  2. package/Package.swift +21 -5
  3. package/README.md +147 -3
  4. package/android/build.gradle +7 -0
  5. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +104 -2
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -1
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +39 -1
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchLatestBundleOptions.java +15 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +30 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/providers/ionic/LiveUpdateIonicManager.java +232 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/providers/ionic/LiveUpdateIonicProvider.java +41 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/providers/ionic/LiveUpdateIonicProviderRegistration.java +46 -0
  17. package/dist/docs.json +151 -3
  18. package/dist/esm/definitions.d.ts +85 -0
  19. package/dist/esm/definitions.js.map +1 -1
  20. package/dist/esm/web.d.ts +2 -1
  21. package/dist/esm/web.js +3 -0
  22. package/dist/esm/web.js.map +1 -1
  23. package/dist/plugin.cjs.js +3 -0
  24. package/dist/plugin.cjs.js.map +1 -1
  25. package/dist/plugin.js +3 -0
  26. package/dist/plugin.js.map +1 -1
  27. package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
  28. package/ios/Plugin/Classes/Options/FetchLatestBundleOptions.swift +12 -0
  29. package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
  30. package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
  31. package/ios/Plugin/Classes/Results/FetchLatestBundleResult.swift +24 -0
  32. package/ios/Plugin/Enums/CustomError.swift +12 -0
  33. package/ios/Plugin/LiveUpdate.swift +55 -1
  34. package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
  35. package/ios/Plugin/LiveUpdatePlugin.swift +40 -1
  36. package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
  37. package/ios/Plugin/Providers/Ionic/LiveUpdateIonicManager.swift +118 -0
  38. package/ios/Plugin/Providers/Ionic/LiveUpdateIonicProvider.swift +25 -0
  39. package/package.json +1 -3
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.api;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import org.json.JSONObject;
5
+
6
+ public class GetChannelsResponseItem {
7
+
8
+ @NonNull
9
+ private final String id;
10
+
11
+ @NonNull
12
+ private final String name;
13
+
14
+ public GetChannelsResponseItem(@NonNull JSONObject responseJson) {
15
+ this.id = responseJson.optString("id");
16
+ this.name = responseJson.optString("name");
17
+ }
18
+
19
+ @NonNull
20
+ public String getId() {
21
+ return id;
22
+ }
23
+
24
+ @NonNull
25
+ public String getName() {
26
+ return name;
27
+ }
28
+ }
@@ -0,0 +1,38 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.options;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+ import com.getcapacitor.PluginCall;
6
+
7
+ public class FetchChannelsOptions {
8
+
9
+ @Nullable
10
+ private final Integer limit;
11
+
12
+ @Nullable
13
+ private final Integer offset;
14
+
15
+ @Nullable
16
+ private final String query;
17
+
18
+ public FetchChannelsOptions(@NonNull PluginCall call) {
19
+ this.limit = call.getInt("limit");
20
+ this.offset = call.getInt("offset");
21
+ this.query = call.getString("query");
22
+ }
23
+
24
+ @Nullable
25
+ public Integer getLimit() {
26
+ return limit;
27
+ }
28
+
29
+ @Nullable
30
+ public Integer getOffset() {
31
+ return offset;
32
+ }
33
+
34
+ @Nullable
35
+ public String getQuery() {
36
+ return query;
37
+ }
38
+ }
@@ -6,17 +6,32 @@ import com.getcapacitor.PluginCall;
6
6
 
7
7
  public class FetchLatestBundleOptions {
8
8
 
9
+ @Nullable
10
+ private final String appId;
11
+
9
12
  @Nullable
10
13
  private final String channel;
11
14
 
12
15
  public FetchLatestBundleOptions(@NonNull PluginCall call) {
16
+ this.appId = null;
13
17
  this.channel = call.getString("channel", null);
14
18
  }
15
19
 
16
20
  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;
17
27
  this.channel = channel;
18
28
  }
19
29
 
30
+ @Nullable
31
+ public String getAppId() {
32
+ return appId;
33
+ }
34
+
20
35
  @Nullable
21
36
  public String getChannel() {
22
37
  return channel;
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.results;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import com.getcapacitor.JSObject;
5
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
6
+
7
+ public class ChannelResult implements Result {
8
+
9
+ @NonNull
10
+ private final String id;
11
+
12
+ @NonNull
13
+ private final String name;
14
+
15
+ public ChannelResult(@NonNull String id, @NonNull String name) {
16
+ this.id = id;
17
+ this.name = name;
18
+ }
19
+
20
+ @NonNull
21
+ @Override
22
+ public JSObject toJSObject() {
23
+ JSObject result = new JSObject();
24
+ result.put("id", id);
25
+ result.put("name", name);
26
+ return result;
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.results;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import com.getcapacitor.JSArray;
5
+ import com.getcapacitor.JSObject;
6
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
7
+
8
+ public class FetchChannelsResult implements Result {
9
+
10
+ @NonNull
11
+ private final ChannelResult[] channels;
12
+
13
+ public FetchChannelsResult(@NonNull ChannelResult[] channels) {
14
+ this.channels = channels;
15
+ }
16
+
17
+ @NonNull
18
+ @Override
19
+ public JSObject toJSObject() {
20
+ JSObject result = new JSObject();
21
+ JSArray channelsArray = new JSArray();
22
+ for (ChannelResult channel : channels) {
23
+ channelsArray.put(channel.toJSObject());
24
+ }
25
+ result.put("channels", channelsArray);
26
+ return result;
27
+ }
28
+ }
@@ -43,6 +43,36 @@ 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
+
46
76
  @NonNull
47
77
  public JSObject toJSObject() {
48
78
  JSObject result = new JSObject();
@@ -0,0 +1,232 @@
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
+ }
@@ -0,0 +1,41 @@
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
+ }
@@ -0,0 +1,46 @@
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
+ }