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