@otakit/capacitor-updater 2.0.0 → 2.1.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/{UpdatekitUpdater.podspec → OtaKitUpdater.podspec} +1 -1
- package/README.md +5 -8
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleInfo.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStatus.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStore.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/DateUtils.java +1 -1
- package/android/src/main/java/com/{updatekit/updater/StatsClient.java → otakit/updater/DeviceEventClient.java} +31 -20
- package/android/src/main/java/com/{updatekit → otakit}/updater/HashUtils.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/HostedManifestKeys.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestClient.java +19 -9
- package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestVerifier.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/UpdaterPlugin.java +74 -105
- package/android/src/main/java/com/{updatekit → otakit}/updater/ZipUtils.java +1 -1
- package/dist/esm/definitions.d.ts +12 -42
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -14
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +4 -9
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +3 -12
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +5 -26
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +5 -26
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/UpdaterPlugin/{StatsClient.swift → DeviceEventClient.swift} +21 -21
- package/ios/Sources/UpdaterPlugin/Downloader.swift +1 -1
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +7 -3
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +2 -5
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +67 -104
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'OtaKitUpdater'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.license = package['license']
|
package/README.md
CHANGED
|
@@ -40,9 +40,6 @@ For manual mode, `getState()` tells you if something is already staged,
|
|
|
40
40
|
`check()` tells you whether a newer update exists, `download()` stages it, and
|
|
41
41
|
`update()` is the one-shot helper that downloads and applies the newest update.
|
|
42
42
|
|
|
43
|
-
Manual inspection and support methods like state inspection and reset live under
|
|
44
|
-
`OtaKit.debug`.
|
|
45
|
-
|
|
46
43
|
## Hosted config
|
|
47
44
|
|
|
48
45
|
```ts
|
|
@@ -63,11 +60,12 @@ plugins: {
|
|
|
63
60
|
Advanced overrides for self-hosting or custom trust only:
|
|
64
61
|
|
|
65
62
|
- `cdnUrl` for manifest and bundle delivery
|
|
66
|
-
- `
|
|
63
|
+
- `ingestUrl` for event ingest requests
|
|
64
|
+
- `serverUrl` for self-hosted control-plane tooling such as the CLI. The native runtime uses `cdnUrl` and `ingestUrl` instead.
|
|
67
65
|
- `manifestKeys`
|
|
68
66
|
- `allowInsecureUrls`
|
|
69
67
|
|
|
70
|
-
Hosted OtaKit already points at the managed
|
|
68
|
+
Hosted OtaKit already points at the managed ingest service and CDN and already
|
|
71
69
|
trusts the managed manifest signing keys.
|
|
72
70
|
|
|
73
71
|
## Channels vs runtimeVersion
|
|
@@ -121,7 +119,7 @@ resume**.
|
|
|
121
119
|
no automatic checks, no automatic staged activation.
|
|
122
120
|
the app integration drives everything via `check()`, `download()`, `apply()`, or `update()`.
|
|
123
121
|
|
|
124
|
-
###
|
|
122
|
+
### Development mode
|
|
125
123
|
|
|
126
124
|
- `immediate`
|
|
127
125
|
checks, downloads, and activates in one shot as soon as possible on cold start and resume (the user may briefly see the previous version before a reload).
|
|
@@ -212,7 +210,6 @@ always live, and `immediate` mode ignores the interval entirely.
|
|
|
212
210
|
- the builtin, current, fallback, and staged bundles are protected
|
|
213
211
|
- superseded staged bundles are deleted automatically
|
|
214
212
|
- failed trial bundles are deleted during rollback
|
|
215
|
-
- `debug.deleteBundle()` only works for downloaded bundles outside runtime state
|
|
216
213
|
|
|
217
214
|
## Source areas
|
|
218
215
|
|
|
@@ -220,7 +217,7 @@ always live, and `immediate` mode ignores the interval entirely.
|
|
|
220
217
|
- `src/index.ts`: Capacitor registration and JS wrapper
|
|
221
218
|
- `src/web.ts`: web fallback implementation
|
|
222
219
|
- `ios/Sources/UpdaterPlugin/*`: iOS implementation
|
|
223
|
-
- `android/src/main/java/com/
|
|
220
|
+
- `android/src/main/java/com/otakit/updater/*`: Android implementation
|
|
224
221
|
|
|
225
222
|
## Build locally
|
|
226
223
|
|
package/android/build.gradle
CHANGED
|
@@ -17,7 +17,7 @@ buildscript {
|
|
|
17
17
|
apply plugin: 'com.android.library'
|
|
18
18
|
|
|
19
19
|
android {
|
|
20
|
-
namespace "com.
|
|
20
|
+
namespace "com.otakit.updater"
|
|
21
21
|
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
22
22
|
defaultConfig {
|
|
23
23
|
minSdk project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
@@ -1,55 +1,60 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.otakit.updater;
|
|
2
2
|
|
|
3
3
|
import java.io.OutputStream;
|
|
4
4
|
import java.net.HttpURLConnection;
|
|
5
5
|
import java.net.URL;
|
|
6
6
|
import java.nio.charset.StandardCharsets;
|
|
7
|
+
import java.text.SimpleDateFormat;
|
|
8
|
+
import java.util.Date;
|
|
9
|
+
import java.util.Locale;
|
|
10
|
+
import java.util.TimeZone;
|
|
7
11
|
import java.util.concurrent.ExecutorService;
|
|
8
12
|
import java.util.concurrent.Executors;
|
|
13
|
+
import java.util.UUID;
|
|
9
14
|
import org.json.JSONObject;
|
|
10
15
|
|
|
11
|
-
final class
|
|
16
|
+
final class DeviceEventClient {
|
|
12
17
|
|
|
13
18
|
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
14
19
|
|
|
15
|
-
private
|
|
20
|
+
private DeviceEventClient() {}
|
|
16
21
|
|
|
17
22
|
static void send(
|
|
18
|
-
String
|
|
23
|
+
String ingestUrl,
|
|
19
24
|
String appId,
|
|
20
25
|
String platform,
|
|
21
26
|
String action,
|
|
22
27
|
String bundleVersion,
|
|
23
28
|
String channel,
|
|
29
|
+
String runtimeVersion,
|
|
24
30
|
String releaseId,
|
|
25
31
|
String nativeBuild,
|
|
26
|
-
String
|
|
32
|
+
String detail
|
|
27
33
|
) {
|
|
28
34
|
executor.execute(() -> {
|
|
29
35
|
HttpURLConnection connection = null;
|
|
30
36
|
try {
|
|
31
|
-
String base =
|
|
32
|
-
URL url = new URL(base + "/
|
|
37
|
+
String base = ingestUrl.replaceAll("/+$", "");
|
|
38
|
+
URL url = new URL(base + "/events");
|
|
33
39
|
|
|
34
40
|
JSONObject payload = new JSONObject();
|
|
41
|
+
payload.put("eventId", UUID.randomUUID().toString());
|
|
42
|
+
payload.put("sentAt", iso8601Now());
|
|
35
43
|
payload.put("platform", platform);
|
|
36
44
|
payload.put("action", action);
|
|
37
|
-
|
|
38
|
-
payload.put("bundleVersion", bundleVersion);
|
|
39
|
-
}
|
|
45
|
+
payload.put("bundleVersion", bundleVersion);
|
|
40
46
|
if (channel != null && !channel.isEmpty()) {
|
|
41
47
|
payload.put("channel", channel);
|
|
42
48
|
}
|
|
43
|
-
if (
|
|
44
|
-
payload.put("
|
|
45
|
-
}
|
|
46
|
-
if (nativeBuild != null) {
|
|
47
|
-
payload.put("nativeBuild", nativeBuild);
|
|
49
|
+
if (runtimeVersion != null && !runtimeVersion.isEmpty()) {
|
|
50
|
+
payload.put("runtimeVersion", runtimeVersion);
|
|
48
51
|
}
|
|
49
|
-
|
|
52
|
+
payload.put("releaseId", releaseId);
|
|
53
|
+
payload.put("nativeBuild", nativeBuild);
|
|
54
|
+
if (detail != null) {
|
|
50
55
|
String truncated =
|
|
51
|
-
|
|
52
|
-
payload.put("
|
|
56
|
+
detail.length() > 500 ? detail.substring(0, 500) : detail;
|
|
57
|
+
payload.put("detail", truncated);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
byte[] body = payload.toString().getBytes(StandardCharsets.UTF_8);
|
|
@@ -66,10 +71,10 @@ final class StatsClient {
|
|
|
66
71
|
output.write(body);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
//
|
|
74
|
+
// Device events are best-effort and should never block the update flow.
|
|
70
75
|
connection.getResponseCode();
|
|
71
76
|
} catch (Exception ignored) {
|
|
72
|
-
//
|
|
77
|
+
// Device events are best-effort, don't fail on errors
|
|
73
78
|
} finally {
|
|
74
79
|
if (connection != null) {
|
|
75
80
|
connection.disconnect();
|
|
@@ -77,4 +82,10 @@ final class StatsClient {
|
|
|
77
82
|
}
|
|
78
83
|
});
|
|
79
84
|
}
|
|
85
|
+
|
|
86
|
+
private static String iso8601Now() {
|
|
87
|
+
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
|
88
|
+
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
89
|
+
return formatter.format(new Date());
|
|
90
|
+
}
|
|
80
91
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.otakit.updater;
|
|
2
2
|
|
|
3
3
|
import java.io.ByteArrayOutputStream;
|
|
4
4
|
import java.io.InputStream;
|
|
@@ -123,14 +123,20 @@ final class ManifestClient {
|
|
|
123
123
|
|
|
124
124
|
String releaseId = null;
|
|
125
125
|
if (json.has("releaseId") && !json.isNull("releaseId")) {
|
|
126
|
-
releaseId = json.getString("releaseId");
|
|
126
|
+
releaseId = json.getString("releaseId").trim();
|
|
127
|
+
}
|
|
128
|
+
if (releaseId != null && releaseId.isEmpty()) {
|
|
129
|
+
releaseId = null;
|
|
130
|
+
}
|
|
131
|
+
if (releaseId == null) {
|
|
132
|
+
throw new IllegalStateException("Manifest response missing required releaseId");
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
requireHTTPS(new URL(downloadUrl), allowInsecureUrls);
|
|
130
136
|
|
|
131
137
|
if (manifestKeys == null || manifestKeys.isEmpty()) {
|
|
132
138
|
android.util.Log.w(
|
|
133
|
-
"
|
|
139
|
+
"OtaKit",
|
|
134
140
|
"No manifest signing keys configured — signature verification is disabled for this request."
|
|
135
141
|
);
|
|
136
142
|
}
|
|
@@ -188,11 +194,15 @@ final class ManifestClient {
|
|
|
188
194
|
if (!sigObj.has("kid") || !sigObj.has("sig") || !sigObj.has("iat") || !sigObj.has("exp")) {
|
|
189
195
|
return null;
|
|
190
196
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
try {
|
|
198
|
+
return new ManifestSignature(
|
|
199
|
+
sigObj.getString("kid"),
|
|
200
|
+
sigObj.getString("sig"),
|
|
201
|
+
sigObj.getInt("iat"),
|
|
202
|
+
sigObj.getInt("exp")
|
|
203
|
+
);
|
|
204
|
+
} catch (org.json.JSONException e) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
197
207
|
}
|
|
198
208
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.otakit.updater;
|
|
2
2
|
|
|
3
3
|
import android.content.pm.PackageInfo;
|
|
4
4
|
import android.content.pm.PackageManager;
|
|
@@ -36,7 +36,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
36
36
|
private int appReadyTimeoutMs = 10_000;
|
|
37
37
|
private boolean allowInsecureUrls = false;
|
|
38
38
|
private String updateMode = UPDATE_MODE_NEXT_LAUNCH;
|
|
39
|
-
private String
|
|
39
|
+
private String ingestUrl;
|
|
40
40
|
private String cdnUrl;
|
|
41
41
|
private String appId;
|
|
42
42
|
private String channel;
|
|
@@ -44,9 +44,9 @@ public class UpdaterPlugin extends Plugin {
|
|
|
44
44
|
private java.util.List<ManifestVerifier.KeyEntry> manifestKeys = new java.util.ArrayList<>();
|
|
45
45
|
private long checkIntervalMs = 600_000;
|
|
46
46
|
private final AtomicBoolean checkInProgress = new AtomicBoolean(false);
|
|
47
|
-
private static final String
|
|
47
|
+
private static final String DEFAULT_INGEST_URL = "https://ingest.otakit.app/v1";
|
|
48
48
|
private static final String DEFAULT_CDN_URL = "https://cdn.otakit.app";
|
|
49
|
-
private static final String
|
|
49
|
+
private static final String INGEST_PATH_SUFFIX = "/v1";
|
|
50
50
|
private static final String UPDATE_MODE_MANUAL = "manual";
|
|
51
51
|
private static final String UPDATE_MODE_NEXT_LAUNCH = "next-launch";
|
|
52
52
|
private static final String UPDATE_MODE_NEXT_RESUME = "next-resume";
|
|
@@ -74,14 +74,13 @@ public class UpdaterPlugin extends Plugin {
|
|
|
74
74
|
}
|
|
75
75
|
} catch (PackageManager.NameNotFoundException ignored) {}
|
|
76
76
|
|
|
77
|
-
this.
|
|
78
|
-
getConfig().getString("
|
|
79
|
-
System.getenv("
|
|
77
|
+
this.ingestUrl = resolveIngestUrl(
|
|
78
|
+
getConfig().getString("ingestUrl"),
|
|
79
|
+
System.getenv("OTAKIT_INGEST_URL")
|
|
80
80
|
);
|
|
81
81
|
this.cdnUrl = resolveCdnUrl(
|
|
82
82
|
getConfig().getString("cdnUrl"),
|
|
83
|
-
System.getenv("OTAKIT_CDN_URL")
|
|
84
|
-
this.updateUrl
|
|
83
|
+
System.getenv("OTAKIT_CDN_URL")
|
|
85
84
|
);
|
|
86
85
|
this.appId = getConfig().getString("appId");
|
|
87
86
|
this.channel = trimToNull(getConfig().getString("channel"));
|
|
@@ -108,7 +107,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
108
107
|
}
|
|
109
108
|
if (manifestKeys.isEmpty()) {
|
|
110
109
|
android.util.Log.e(
|
|
111
|
-
"
|
|
110
|
+
"OtaKit",
|
|
112
111
|
"manifestKeys configured but all entries are invalid. Manifest verification will reject all updates."
|
|
113
112
|
);
|
|
114
113
|
manifestKeys.add(new ManifestVerifier.KeyEntry("_invalid_", new byte[0]));
|
|
@@ -116,7 +115,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
116
115
|
}
|
|
117
116
|
} catch (Exception e) {
|
|
118
117
|
android.util.Log.e(
|
|
119
|
-
"
|
|
118
|
+
"OtaKit",
|
|
120
119
|
"Failed to parse manifestKeys. Manifest verification will reject all updates.",
|
|
121
120
|
e
|
|
122
121
|
);
|
|
@@ -274,7 +273,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
274
273
|
}
|
|
275
274
|
|
|
276
275
|
@PluginMethod
|
|
277
|
-
public void
|
|
276
|
+
public void getState(PluginCall call) {
|
|
278
277
|
JSObject result = new JSObject();
|
|
279
278
|
result.put("current", store.getCurrentBundle().toJSObject());
|
|
280
279
|
result.put("fallback", store.getFallbackBundle().toJSObject());
|
|
@@ -402,7 +401,14 @@ public class UpdaterPlugin extends Plugin {
|
|
|
402
401
|
BundleInfo updated = store.getBundle(current.id);
|
|
403
402
|
notifyListeners("appReady", updated != null ? updated.toJSObject() : current.toJSObject());
|
|
404
403
|
|
|
405
|
-
|
|
404
|
+
sendDeviceEvent(
|
|
405
|
+
"applied",
|
|
406
|
+
current.version,
|
|
407
|
+
current.runtimeVersion,
|
|
408
|
+
current.channel,
|
|
409
|
+
current.releaseId,
|
|
410
|
+
null
|
|
411
|
+
);
|
|
406
412
|
|
|
407
413
|
if (!oldFallback.isBuiltin() && !oldFallback.id.equals(current.id)) {
|
|
408
414
|
try {
|
|
@@ -415,65 +421,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
415
421
|
}
|
|
416
422
|
|
|
417
423
|
@PluginMethod
|
|
418
|
-
public void
|
|
419
|
-
cancelTrialTimeout();
|
|
420
|
-
store.setCurrentBundleId(null);
|
|
421
|
-
store.setStagedBundleId(null);
|
|
422
|
-
store.setFallbackBundleId(null);
|
|
423
|
-
store.setFailedBundle(null);
|
|
424
|
-
applyServerBasePath(null);
|
|
425
|
-
call.resolve();
|
|
426
|
-
reloadWebView();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
@PluginMethod
|
|
430
|
-
public void debugListBundles(PluginCall call) {
|
|
431
|
-
JSObject result = new JSObject();
|
|
432
|
-
JSArray bundles = store.listDownloadedBundles();
|
|
433
|
-
result.put("bundles", bundles);
|
|
434
|
-
call.resolve(result);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
@PluginMethod
|
|
438
|
-
public void debugDeleteBundle(PluginCall call) {
|
|
439
|
-
String bundleId = call.getString("bundleId");
|
|
440
|
-
if (bundleId == null) {
|
|
441
|
-
call.reject("Missing bundleId");
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (!store.bundleExists(bundleId)) {
|
|
445
|
-
call.reject("Bundle not found");
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
BundleInfo current = store.getCurrentBundle();
|
|
450
|
-
if (bundleId.equals(current.id)) {
|
|
451
|
-
call.reject("Cannot delete current bundle");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
BundleInfo fallback = store.getFallbackBundle();
|
|
456
|
-
if (bundleId.equals(fallback.id)) {
|
|
457
|
-
call.reject("Cannot delete fallback bundle");
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
String stagedId = store.getStagedBundleId();
|
|
462
|
-
if (bundleId.equals(stagedId)) {
|
|
463
|
-
call.reject("Cannot delete staged bundle");
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
store.deleteBundle(bundleId);
|
|
469
|
-
call.resolve();
|
|
470
|
-
} catch (Exception e) {
|
|
471
|
-
call.reject("Delete failed: " + e.getMessage());
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
@PluginMethod
|
|
476
|
-
public void debugGetLastFailure(PluginCall call) {
|
|
424
|
+
public void getLastFailure(PluginCall call) {
|
|
477
425
|
BundleInfo failed = store.getFailedBundle();
|
|
478
426
|
if (failed == null) {
|
|
479
427
|
call.resolve((JSObject) null);
|
|
@@ -589,7 +537,14 @@ public class UpdaterPlugin extends Plugin {
|
|
|
589
537
|
long requiredSpace = (long) (expectedSize * 2.5); // zip + extracted + buffer
|
|
590
538
|
long availableSpace = getFreeDiskSpace();
|
|
591
539
|
if (availableSpace < requiredSpace) {
|
|
592
|
-
|
|
540
|
+
sendDeviceEvent(
|
|
541
|
+
"download_error",
|
|
542
|
+
version,
|
|
543
|
+
runtimeVersion,
|
|
544
|
+
channel,
|
|
545
|
+
releaseId,
|
|
546
|
+
"insufficient_disk_space"
|
|
547
|
+
);
|
|
593
548
|
throw new IllegalStateException("Insufficient disk space");
|
|
594
549
|
}
|
|
595
550
|
}
|
|
@@ -609,7 +564,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
609
564
|
|
|
610
565
|
extractedDirectory = new File(
|
|
611
566
|
getContext().getCacheDir(),
|
|
612
|
-
"
|
|
567
|
+
"otakit-extract-" + System.currentTimeMillis()
|
|
613
568
|
);
|
|
614
569
|
if (!extractedDirectory.exists() && !extractedDirectory.mkdirs()) {
|
|
615
570
|
throw new IllegalStateException("Cannot create temporary extraction directory");
|
|
@@ -642,14 +597,14 @@ public class UpdaterPlugin extends Plugin {
|
|
|
642
597
|
cleanupSupersededStagedBundle(previousStagedId, bundleId);
|
|
643
598
|
|
|
644
599
|
notifyListeners("downloadComplete", info.toJSObject());
|
|
645
|
-
|
|
600
|
+
sendDeviceEvent("downloaded", version, runtimeVersion, channel, releaseId, null);
|
|
646
601
|
return info;
|
|
647
602
|
} catch (Exception e) {
|
|
648
603
|
JSObject failed = new JSObject();
|
|
649
604
|
failed.put("version", version);
|
|
650
605
|
failed.put("error", e.getMessage());
|
|
651
606
|
notifyListeners("downloadFailed", failed);
|
|
652
|
-
|
|
607
|
+
sendDeviceEvent("download_error", version, runtimeVersion, channel, releaseId, e.getMessage());
|
|
653
608
|
throw e;
|
|
654
609
|
} finally {
|
|
655
610
|
if (downloadedZip != null && downloadedZip.exists()) {
|
|
@@ -677,7 +632,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
677
632
|
throw new IllegalStateException("Download failed with HTTP " + status);
|
|
678
633
|
}
|
|
679
634
|
|
|
680
|
-
File destination = File.createTempFile("
|
|
635
|
+
File destination = File.createTempFile("otakit-", ".zip", getContext().getCacheDir());
|
|
681
636
|
|
|
682
637
|
try (
|
|
683
638
|
InputStream input = connection.getInputStream();
|
|
@@ -803,7 +758,14 @@ public class UpdaterPlugin extends Plugin {
|
|
|
803
758
|
store.setFailedBundle(failed);
|
|
804
759
|
store.setStagedBundleId(null);
|
|
805
760
|
|
|
806
|
-
|
|
761
|
+
sendDeviceEvent(
|
|
762
|
+
"rollback",
|
|
763
|
+
current.version,
|
|
764
|
+
current.runtimeVersion,
|
|
765
|
+
current.channel,
|
|
766
|
+
current.releaseId,
|
|
767
|
+
reason
|
|
768
|
+
);
|
|
807
769
|
|
|
808
770
|
BundleInfo fallback = store.getFallbackBundle();
|
|
809
771
|
JSObject payload = new JSObject();
|
|
@@ -883,9 +845,7 @@ public class UpdaterPlugin extends Plugin {
|
|
|
883
845
|
if (latest.runtimeVersion != null) {
|
|
884
846
|
object.put("runtimeVersion", latest.runtimeVersion);
|
|
885
847
|
}
|
|
886
|
-
|
|
887
|
-
object.put("releaseId", latest.releaseId);
|
|
888
|
-
}
|
|
848
|
+
object.put("releaseId", latest.releaseId);
|
|
889
849
|
return object;
|
|
890
850
|
}
|
|
891
851
|
|
|
@@ -1043,21 +1003,21 @@ public class UpdaterPlugin extends Plugin {
|
|
|
1043
1003
|
return normalized + "-" + suffix;
|
|
1044
1004
|
}
|
|
1045
1005
|
|
|
1046
|
-
private String
|
|
1006
|
+
private String resolveIngestUrl(String configured, String env) {
|
|
1047
1007
|
String configuredValue = trimToNull(configured);
|
|
1048
1008
|
if (configuredValue != null) {
|
|
1049
|
-
return
|
|
1009
|
+
return normalizeIngestUrl(configuredValue);
|
|
1050
1010
|
}
|
|
1051
1011
|
|
|
1052
1012
|
String envValue = trimToNull(env);
|
|
1053
1013
|
if (envValue != null) {
|
|
1054
|
-
return
|
|
1014
|
+
return normalizeIngestUrl(envValue);
|
|
1055
1015
|
}
|
|
1056
1016
|
|
|
1057
|
-
return
|
|
1017
|
+
return DEFAULT_INGEST_URL;
|
|
1058
1018
|
}
|
|
1059
1019
|
|
|
1060
|
-
private String resolveCdnUrl(String configured, String env
|
|
1020
|
+
private String resolveCdnUrl(String configured, String env) {
|
|
1061
1021
|
String configuredValue = trimToNull(configured);
|
|
1062
1022
|
if (configuredValue != null) {
|
|
1063
1023
|
return normalizeCdnUrl(configuredValue);
|
|
@@ -1068,23 +1028,15 @@ public class UpdaterPlugin extends Plugin {
|
|
|
1068
1028
|
return normalizeCdnUrl(envValue);
|
|
1069
1029
|
}
|
|
1070
1030
|
|
|
1071
|
-
if (
|
|
1072
|
-
resolvedUpdateUrl != null &&
|
|
1073
|
-
!DEFAULT_UPDATE_URL.equalsIgnoreCase(resolvedUpdateUrl) &&
|
|
1074
|
-
resolvedUpdateUrl.toLowerCase(java.util.Locale.ROOT).endsWith(API_PATH_SUFFIX)
|
|
1075
|
-
) {
|
|
1076
|
-
return resolvedUpdateUrl.substring(0, resolvedUpdateUrl.length() - API_PATH_SUFFIX.length());
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
1031
|
return DEFAULT_CDN_URL;
|
|
1080
1032
|
}
|
|
1081
1033
|
|
|
1082
|
-
private String
|
|
1034
|
+
private String normalizeIngestUrl(String raw) {
|
|
1083
1035
|
String trimmed = raw.trim().replaceAll("/+$", "");
|
|
1084
|
-
if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(
|
|
1036
|
+
if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(INGEST_PATH_SUFFIX)) {
|
|
1085
1037
|
return trimmed;
|
|
1086
1038
|
}
|
|
1087
|
-
return trimmed +
|
|
1039
|
+
return trimmed + INGEST_PATH_SUFFIX;
|
|
1088
1040
|
}
|
|
1089
1041
|
|
|
1090
1042
|
private String normalizeCdnUrl(String raw) {
|
|
@@ -1105,26 +1057,43 @@ public class UpdaterPlugin extends Plugin {
|
|
|
1105
1057
|
return resolved != null ? resolved : this.channel;
|
|
1106
1058
|
}
|
|
1107
1059
|
|
|
1108
|
-
private void
|
|
1060
|
+
private void sendDeviceEvent(
|
|
1109
1061
|
String action,
|
|
1110
1062
|
String bundleVersion,
|
|
1063
|
+
String runtimeVersion,
|
|
1111
1064
|
String channel,
|
|
1112
1065
|
String releaseId,
|
|
1113
|
-
String
|
|
1066
|
+
String detail
|
|
1114
1067
|
) {
|
|
1115
1068
|
if (appId == null) {
|
|
1116
1069
|
return;
|
|
1117
1070
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1071
|
+
String normalizedBundleVersion = trimToNull(bundleVersion);
|
|
1072
|
+
if (normalizedBundleVersion == null) {
|
|
1073
|
+
android.util.Log.w("OtaKit", "Skipping device event without bundleVersion");
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
String normalizedReleaseId = trimToNull(releaseId);
|
|
1077
|
+
if (normalizedReleaseId == null) {
|
|
1078
|
+
android.util.Log.w("OtaKit", "Skipping device event without releaseId");
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
String nativeBuild = trimToNull(store.getNativeBuild());
|
|
1082
|
+
if (nativeBuild == null) {
|
|
1083
|
+
android.util.Log.w("OtaKit", "Skipping device event without nativeBuild");
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
DeviceEventClient.send(
|
|
1087
|
+
ingestUrl,
|
|
1120
1088
|
appId,
|
|
1121
1089
|
"android",
|
|
1122
1090
|
action,
|
|
1123
|
-
|
|
1091
|
+
normalizedBundleVersion,
|
|
1124
1092
|
channel,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1093
|
+
trimToNull(runtimeVersion),
|
|
1094
|
+
normalizedReleaseId,
|
|
1095
|
+
nativeBuild,
|
|
1096
|
+
detail
|
|
1128
1097
|
);
|
|
1129
1098
|
}
|
|
1130
1099
|
|