@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.
Files changed (33) hide show
  1. package/{UpdatekitUpdater.podspec → OtaKitUpdater.podspec} +1 -1
  2. package/README.md +5 -8
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleInfo.java +1 -1
  5. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStatus.java +1 -1
  6. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStore.java +1 -1
  7. package/android/src/main/java/com/{updatekit → otakit}/updater/DateUtils.java +1 -1
  8. package/android/src/main/java/com/{updatekit/updater/StatsClient.java → otakit/updater/DeviceEventClient.java} +31 -20
  9. package/android/src/main/java/com/{updatekit → otakit}/updater/HashUtils.java +1 -1
  10. package/android/src/main/java/com/{updatekit → otakit}/updater/HostedManifestKeys.java +1 -1
  11. package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestClient.java +19 -9
  12. package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestVerifier.java +1 -1
  13. package/android/src/main/java/com/{updatekit → otakit}/updater/UpdaterPlugin.java +74 -105
  14. package/android/src/main/java/com/{updatekit → otakit}/updater/ZipUtils.java +1 -1
  15. package/dist/esm/definitions.d.ts +12 -42
  16. package/dist/esm/definitions.d.ts.map +1 -1
  17. package/dist/esm/index.d.ts.map +1 -1
  18. package/dist/esm/index.js +2 -14
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/web.d.ts +4 -9
  21. package/dist/esm/web.d.ts.map +1 -1
  22. package/dist/esm/web.js +3 -12
  23. package/dist/esm/web.js.map +1 -1
  24. package/dist/plugin.cjs.js +5 -26
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +5 -26
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/UpdaterPlugin/{StatsClient.swift → DeviceEventClient.swift} +21 -21
  29. package/ios/Sources/UpdaterPlugin/Downloader.swift +1 -1
  30. package/ios/Sources/UpdaterPlugin/ManifestClient.swift +7 -3
  31. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +2 -5
  32. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +67 -104
  33. 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 = 'UpdatekitUpdater'
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
- - `serverUrl` for stats and control-plane requests
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 control-plane API and CDN and already
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
- ### Dev/debug mode
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/updatekit/updater/*`: Android implementation
220
+ - `android/src/main/java/com/otakit/updater/*`: Android implementation
224
221
 
225
222
  ## Build locally
226
223
 
@@ -17,7 +17,7 @@ buildscript {
17
17
  apply plugin: 'com.android.library'
18
18
 
19
19
  android {
20
- namespace "com.updatekit.updater"
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,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import com.getcapacitor.JSObject;
4
4
  import org.json.JSONException;
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  enum BundleStatus {
4
4
  BUILTIN("builtin"),
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import java.text.SimpleDateFormat;
4
4
  import java.util.Date;
@@ -1,55 +1,60 @@
1
- package com.updatekit.updater;
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 StatsClient {
16
+ final class DeviceEventClient {
12
17
 
13
18
  private static final ExecutorService executor = Executors.newSingleThreadExecutor();
14
19
 
15
- private StatsClient() {}
20
+ private DeviceEventClient() {}
16
21
 
17
22
  static void send(
18
- String updateUrl,
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 errorMessage
32
+ String detail
27
33
  ) {
28
34
  executor.execute(() -> {
29
35
  HttpURLConnection connection = null;
30
36
  try {
31
- String base = updateUrl.replaceAll("/+$", "");
32
- URL url = new URL(base + "/stats");
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
- if (bundleVersion != null) {
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 (releaseId != null && !releaseId.isEmpty()) {
44
- payload.put("releaseId", releaseId);
45
- }
46
- if (nativeBuild != null) {
47
- payload.put("nativeBuild", nativeBuild);
49
+ if (runtimeVersion != null && !runtimeVersion.isEmpty()) {
50
+ payload.put("runtimeVersion", runtimeVersion);
48
51
  }
49
- if (errorMessage != null) {
52
+ payload.put("releaseId", releaseId);
53
+ payload.put("nativeBuild", nativeBuild);
54
+ if (detail != null) {
50
55
  String truncated =
51
- errorMessage.length() > 500 ? errorMessage.substring(0, 500) : errorMessage;
52
- payload.put("errorMessage", truncated);
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
- // Fire and forget - just trigger the request
74
+ // Device events are best-effort and should never block the update flow.
70
75
  connection.getResponseCode();
71
76
  } catch (Exception ignored) {
72
- // Stats are best-effort, don't fail on errors
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.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import java.io.File;
4
4
  import java.io.FileInputStream;
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import android.util.Base64;
4
4
  import java.util.ArrayList;
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
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
- "UpdateKit",
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
- return new ManifestSignature(
192
- sigObj.getString("kid"),
193
- sigObj.getString("sig"),
194
- sigObj.getInt("iat"),
195
- sigObj.getInt("exp")
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.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import android.util.Base64;
4
4
  import java.security.KeyFactory;
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
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 updateUrl;
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 DEFAULT_UPDATE_URL = "https://www.otakit.app/api/v1";
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 API_PATH_SUFFIX = "/api/v1";
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.updateUrl = resolveUpdateUrl(
78
- getConfig().getString("serverUrl"),
79
- System.getenv("OTAKIT_SERVER_URL")
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
- "UpdateKit",
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
- "UpdateKit",
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 debugGetState(PluginCall call) {
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
- sendStats("applied", current.version, current.channel, current.releaseId, null);
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 debugReset(PluginCall call) {
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
- sendStats("download_error", version, channel, releaseId, "insufficient_disk_space");
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
- "updatekit-extract-" + System.currentTimeMillis()
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
- sendStats("downloaded", version, channel, releaseId, null);
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
- sendStats("download_error", version, channel, releaseId, e.getMessage());
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("updatekit-", ".zip", getContext().getCacheDir());
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
- sendStats("rollback", current.version, current.channel, current.releaseId, reason);
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
- if (latest.releaseId != null) {
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 resolveUpdateUrl(String configured, String env) {
1006
+ private String resolveIngestUrl(String configured, String env) {
1047
1007
  String configuredValue = trimToNull(configured);
1048
1008
  if (configuredValue != null) {
1049
- return normalizeUpdateUrl(configuredValue);
1009
+ return normalizeIngestUrl(configuredValue);
1050
1010
  }
1051
1011
 
1052
1012
  String envValue = trimToNull(env);
1053
1013
  if (envValue != null) {
1054
- return normalizeUpdateUrl(envValue);
1014
+ return normalizeIngestUrl(envValue);
1055
1015
  }
1056
1016
 
1057
- return DEFAULT_UPDATE_URL;
1017
+ return DEFAULT_INGEST_URL;
1058
1018
  }
1059
1019
 
1060
- private String resolveCdnUrl(String configured, String env, String resolvedUpdateUrl) {
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 normalizeUpdateUrl(String raw) {
1034
+ private String normalizeIngestUrl(String raw) {
1083
1035
  String trimmed = raw.trim().replaceAll("/+$", "");
1084
- if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(API_PATH_SUFFIX)) {
1036
+ if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(INGEST_PATH_SUFFIX)) {
1085
1037
  return trimmed;
1086
1038
  }
1087
- return trimmed + API_PATH_SUFFIX;
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 sendStats(
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 errorMessage
1066
+ String detail
1114
1067
  ) {
1115
1068
  if (appId == null) {
1116
1069
  return;
1117
1070
  }
1118
- StatsClient.send(
1119
- updateUrl,
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
- bundleVersion,
1091
+ normalizedBundleVersion,
1124
1092
  channel,
1125
- releaseId,
1126
- store.getNativeBuild(),
1127
- errorMessage
1093
+ trimToNull(runtimeVersion),
1094
+ normalizedReleaseId,
1095
+ nativeBuild,
1096
+ detail
1128
1097
  );
1129
1098
  }
1130
1099
 
@@ -1,4 +1,4 @@
1
- package com.updatekit.updater;
1
+ package com.otakit.updater;
2
2
 
3
3
  import java.io.File;
4
4
  import java.io.FileInputStream;