@otakit/capacitor-updater 2.0.0 → 2.0.1

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/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).
@@ -4,52 +4,57 @@ 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
  }
@@ -123,7 +123,13 @@ 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);
@@ -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"));
@@ -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 {
@@ -589,7 +595,14 @@ public class UpdaterPlugin extends Plugin {
589
595
  long requiredSpace = (long) (expectedSize * 2.5); // zip + extracted + buffer
590
596
  long availableSpace = getFreeDiskSpace();
591
597
  if (availableSpace < requiredSpace) {
592
- sendStats("download_error", version, channel, releaseId, "insufficient_disk_space");
598
+ sendDeviceEvent(
599
+ "download_error",
600
+ version,
601
+ runtimeVersion,
602
+ channel,
603
+ releaseId,
604
+ "insufficient_disk_space"
605
+ );
593
606
  throw new IllegalStateException("Insufficient disk space");
594
607
  }
595
608
  }
@@ -642,14 +655,14 @@ public class UpdaterPlugin extends Plugin {
642
655
  cleanupSupersededStagedBundle(previousStagedId, bundleId);
643
656
 
644
657
  notifyListeners("downloadComplete", info.toJSObject());
645
- sendStats("downloaded", version, channel, releaseId, null);
658
+ sendDeviceEvent("downloaded", version, runtimeVersion, channel, releaseId, null);
646
659
  return info;
647
660
  } catch (Exception e) {
648
661
  JSObject failed = new JSObject();
649
662
  failed.put("version", version);
650
663
  failed.put("error", e.getMessage());
651
664
  notifyListeners("downloadFailed", failed);
652
- sendStats("download_error", version, channel, releaseId, e.getMessage());
665
+ sendDeviceEvent("download_error", version, runtimeVersion, channel, releaseId, e.getMessage());
653
666
  throw e;
654
667
  } finally {
655
668
  if (downloadedZip != null && downloadedZip.exists()) {
@@ -803,7 +816,14 @@ public class UpdaterPlugin extends Plugin {
803
816
  store.setFailedBundle(failed);
804
817
  store.setStagedBundleId(null);
805
818
 
806
- sendStats("rollback", current.version, current.channel, current.releaseId, reason);
819
+ sendDeviceEvent(
820
+ "rollback",
821
+ current.version,
822
+ current.runtimeVersion,
823
+ current.channel,
824
+ current.releaseId,
825
+ reason
826
+ );
807
827
 
808
828
  BundleInfo fallback = store.getFallbackBundle();
809
829
  JSObject payload = new JSObject();
@@ -883,9 +903,7 @@ public class UpdaterPlugin extends Plugin {
883
903
  if (latest.runtimeVersion != null) {
884
904
  object.put("runtimeVersion", latest.runtimeVersion);
885
905
  }
886
- if (latest.releaseId != null) {
887
- object.put("releaseId", latest.releaseId);
888
- }
906
+ object.put("releaseId", latest.releaseId);
889
907
  return object;
890
908
  }
891
909
 
@@ -1043,21 +1061,21 @@ public class UpdaterPlugin extends Plugin {
1043
1061
  return normalized + "-" + suffix;
1044
1062
  }
1045
1063
 
1046
- private String resolveUpdateUrl(String configured, String env) {
1064
+ private String resolveIngestUrl(String configured, String env) {
1047
1065
  String configuredValue = trimToNull(configured);
1048
1066
  if (configuredValue != null) {
1049
- return normalizeUpdateUrl(configuredValue);
1067
+ return normalizeIngestUrl(configuredValue);
1050
1068
  }
1051
1069
 
1052
1070
  String envValue = trimToNull(env);
1053
1071
  if (envValue != null) {
1054
- return normalizeUpdateUrl(envValue);
1072
+ return normalizeIngestUrl(envValue);
1055
1073
  }
1056
1074
 
1057
- return DEFAULT_UPDATE_URL;
1075
+ return DEFAULT_INGEST_URL;
1058
1076
  }
1059
1077
 
1060
- private String resolveCdnUrl(String configured, String env, String resolvedUpdateUrl) {
1078
+ private String resolveCdnUrl(String configured, String env) {
1061
1079
  String configuredValue = trimToNull(configured);
1062
1080
  if (configuredValue != null) {
1063
1081
  return normalizeCdnUrl(configuredValue);
@@ -1068,23 +1086,15 @@ public class UpdaterPlugin extends Plugin {
1068
1086
  return normalizeCdnUrl(envValue);
1069
1087
  }
1070
1088
 
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
1089
  return DEFAULT_CDN_URL;
1080
1090
  }
1081
1091
 
1082
- private String normalizeUpdateUrl(String raw) {
1092
+ private String normalizeIngestUrl(String raw) {
1083
1093
  String trimmed = raw.trim().replaceAll("/+$", "");
1084
- if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(API_PATH_SUFFIX)) {
1094
+ if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(INGEST_PATH_SUFFIX)) {
1085
1095
  return trimmed;
1086
1096
  }
1087
- return trimmed + API_PATH_SUFFIX;
1097
+ return trimmed + INGEST_PATH_SUFFIX;
1088
1098
  }
1089
1099
 
1090
1100
  private String normalizeCdnUrl(String raw) {
@@ -1105,26 +1115,43 @@ public class UpdaterPlugin extends Plugin {
1105
1115
  return resolved != null ? resolved : this.channel;
1106
1116
  }
1107
1117
 
1108
- private void sendStats(
1118
+ private void sendDeviceEvent(
1109
1119
  String action,
1110
1120
  String bundleVersion,
1121
+ String runtimeVersion,
1111
1122
  String channel,
1112
1123
  String releaseId,
1113
- String errorMessage
1124
+ String detail
1114
1125
  ) {
1115
1126
  if (appId == null) {
1116
1127
  return;
1117
1128
  }
1118
- StatsClient.send(
1119
- updateUrl,
1129
+ String normalizedBundleVersion = trimToNull(bundleVersion);
1130
+ if (normalizedBundleVersion == null) {
1131
+ android.util.Log.w("UpdateKit", "Skipping device event without bundleVersion");
1132
+ return;
1133
+ }
1134
+ String normalizedReleaseId = trimToNull(releaseId);
1135
+ if (normalizedReleaseId == null) {
1136
+ android.util.Log.w("UpdateKit", "Skipping device event without releaseId");
1137
+ return;
1138
+ }
1139
+ String nativeBuild = trimToNull(store.getNativeBuild());
1140
+ if (nativeBuild == null) {
1141
+ android.util.Log.w("UpdateKit", "Skipping device event without nativeBuild");
1142
+ return;
1143
+ }
1144
+ DeviceEventClient.send(
1145
+ ingestUrl,
1120
1146
  appId,
1121
1147
  "android",
1122
1148
  action,
1123
- bundleVersion,
1149
+ normalizedBundleVersion,
1124
1150
  channel,
1125
- releaseId,
1126
- store.getNativeBuild(),
1127
- errorMessage
1151
+ trimToNull(runtimeVersion),
1152
+ normalizedReleaseId,
1153
+ nativeBuild,
1154
+ detail
1128
1155
  );
1129
1156
  }
1130
1157
 
@@ -46,7 +46,7 @@ export interface LatestVersion {
46
46
  /** True when this exact update is already staged locally. */
47
47
  downloaded?: boolean;
48
48
  /** Release history ID associated with this manifest */
49
- releaseId?: string;
49
+ releaseId: string;
50
50
  }
51
51
  export interface BundleListResult {
52
52
  bundles: BundleInfo[];
@@ -68,7 +68,7 @@ export interface OtaKitManifestKey {
68
68
  * Plugin configuration for capacitor.config.ts.
69
69
  */
70
70
  export interface OtaKitConfig {
71
- /** OtaKit app ID used for manifest and stats requests. */
71
+ /** OtaKit app ID used for manifest fetches and event ingest. */
72
72
  appId: string;
73
73
  /** Optional named release track. Omit to use the base channel. */
74
74
  channel?: string;
@@ -84,7 +84,9 @@ export interface OtaKitConfig {
84
84
  checkInterval?: number;
85
85
  /** Milliseconds to wait for notifyAppReady(). Defaults to 10000. */
86
86
  appReadyTimeout?: number;
87
- /** Custom API base URL for stats and other control-plane requests. */
87
+ /** Custom event ingest base URL. Hosted default: https://ingest.otakit.app/v1 */
88
+ ingestUrl?: string;
89
+ /** Optional control-plane API base URL used by self-host tooling such as the CLI. The native runtime does not use it. */
88
90
  serverUrl?: string;
89
91
  /** Custom CDN base URL for manifest and bundle delivery. */
90
92
  cdnUrl?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
@@ -1,52 +1,52 @@
1
1
  import Foundation
2
2
 
3
- enum StatsAction: String {
3
+ enum DeviceEventAction: String {
4
4
  case downloaded
5
5
  case applied
6
6
  case downloadError = "download_error"
7
7
  case rollback
8
8
  }
9
9
 
10
- enum StatsClient {
10
+ enum DeviceEventClient {
11
11
  static func send(
12
- updateUrl: String,
12
+ ingestUrl: String,
13
13
  appId: String,
14
14
  platform: String,
15
- action: StatsAction,
16
- bundleVersion: String?,
15
+ action: DeviceEventAction,
16
+ bundleVersion: String,
17
17
  channel: String?,
18
- releaseId: String?,
19
- nativeBuild: String?,
20
- errorMessage: String?
18
+ runtimeVersion: String?,
19
+ releaseId: String,
20
+ nativeBuild: String,
21
+ detail: String?
21
22
  ) {
22
- let sanitizedBase = updateUrl.replacingOccurrences(
23
+ let sanitizedBase = ingestUrl.replacingOccurrences(
23
24
  of: "/+$",
24
25
  with: "",
25
26
  options: .regularExpression
26
27
  )
27
28
 
28
- guard let url = URL(string: "\(sanitizedBase)/stats") else {
29
+ guard let url = URL(string: "\(sanitizedBase)/events") else {
29
30
  return
30
31
  }
31
32
 
32
33
  var payload: [String: Any] = [
34
+ "eventId": UUID().uuidString.lowercased(),
35
+ "sentAt": ISO8601DateFormatter().string(from: Date()),
33
36
  "platform": platform,
34
37
  "action": action.rawValue,
38
+ "bundleVersion": bundleVersion,
39
+ "releaseId": releaseId,
40
+ "nativeBuild": nativeBuild,
35
41
  ]
36
- if let bundleVersion {
37
- payload["bundleVersion"] = bundleVersion
38
- }
39
42
  if let channel, !channel.isEmpty {
40
43
  payload["channel"] = channel
41
44
  }
42
- if let releaseId, !releaseId.isEmpty {
43
- payload["releaseId"] = releaseId
44
- }
45
- if let nativeBuild {
46
- payload["nativeBuild"] = nativeBuild
45
+ if let runtimeVersion, !runtimeVersion.isEmpty {
46
+ payload["runtimeVersion"] = runtimeVersion
47
47
  }
48
- if let errorMessage {
49
- payload["errorMessage"] = String(errorMessage.prefix(500))
48
+ if let detail {
49
+ payload["detail"] = String(detail.prefix(500))
50
50
  }
51
51
 
52
52
  guard let body = try? JSONSerialization.data(withJSONObject: payload) else {
@@ -60,7 +60,7 @@ enum StatsClient {
60
60
  request.httpBody = body
61
61
  request.timeoutInterval = 10
62
62
 
63
- // Fire and forget - don't block on response
63
+ // Device events are best-effort and should never block the update flow.
64
64
  URLSession.shared.dataTask(with: request).resume()
65
65
  }
66
66
  }
@@ -9,7 +9,7 @@ struct LatestManifest {
9
9
  let sha256: String
10
10
  let size: Int
11
11
  let runtimeVersion: String?
12
- let releaseId: String?
12
+ let releaseId: String
13
13
  }
14
14
 
15
15
  struct ManifestSignature {
@@ -102,7 +102,11 @@ enum ManifestClient {
102
102
  .nilIfEmpty
103
103
 
104
104
  let signature = parseSignature(object["signature"])
105
- let releaseId = object["releaseId"] as? String
105
+ guard let releaseId = (object["releaseId"] as? String)?
106
+ .trimmingCharacters(in: .whitespacesAndNewlines)
107
+ .nilIfEmpty else {
108
+ throw ManifestClientError.invalidResponse
109
+ }
106
110
 
107
111
  guard let dlURL = URL(string: downloadUrl) else {
108
112
  throw ManifestClientError.invalidURL
@@ -38,7 +38,7 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
38
38
  private var appReadyTimeoutMs = 10_000
39
39
  private var allowInsecureUrls = false
40
40
  private var updateMode: UpdateMode = .nextLaunch
41
- private var updateUrl = UpdaterPlugin.defaultUpdateURL
41
+ private var ingestUrl = UpdaterPlugin.defaultIngestURL
42
42
  private var cdnUrl = UpdaterPlugin.defaultCdnURL
43
43
  private var appId: String?
44
44
  private var channel: String?
@@ -49,22 +49,21 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
49
49
  private let isCheckInProgress = NSLock()
50
50
  private var checkInProgress = false
51
51
  private var foregroundObserver: NSObjectProtocol?
52
- private static let defaultUpdateURL = "https://www.otakit.app/api/v1"
52
+ private static let defaultIngestURL = "https://ingest.otakit.app/v1"
53
53
  private static let defaultCdnURL = "https://cdn.otakit.app"
54
- private static let apiPathSuffix = "/api/v1"
54
+ private static let ingestPathSuffix = "/v1"
55
55
  private static let lastCheckTimestampKey = "otakit_last_check_timestamp"
56
56
 
57
57
  public override func load() {
58
- let envUpdateUrl = ProcessInfo.processInfo.environment["OTAKIT_SERVER_URL"]
58
+ let envIngestUrl = ProcessInfo.processInfo.environment["OTAKIT_INGEST_URL"]
59
59
  let envCdnUrl = ProcessInfo.processInfo.environment["OTAKIT_CDN_URL"]
60
- updateUrl = resolveUpdateUrl(
61
- configured: getConfig().getString("serverUrl"),
62
- env: envUpdateUrl
60
+ ingestUrl = resolveIngestUrl(
61
+ configured: getConfig().getString("ingestUrl"),
62
+ env: envIngestUrl
63
63
  )
64
64
  cdnUrl = resolveCdnUrl(
65
65
  configured: getConfig().getString("cdnUrl"),
66
- env: envCdnUrl,
67
- resolvedUpdateUrl: updateUrl
66
+ env: envCdnUrl
68
67
  )
69
68
  appId = getConfig().getString("appId")
70
69
  channel = trimToNil(getConfig().getString("channel"))
@@ -394,9 +393,10 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
394
393
  notifyListeners("appReady", data: current.toDictionary())
395
394
  }
396
395
 
397
- sendStats(
396
+ sendDeviceEvent(
398
397
  action: .applied,
399
398
  bundleVersion: current.version,
399
+ runtimeVersion: current.runtimeVersion,
400
400
  channel: current.channel,
401
401
  releaseId: current.releaseId
402
402
  )
@@ -593,12 +593,13 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
593
593
  code: 1,
594
594
  userInfo: [NSLocalizedDescriptionKey: "Insufficient disk space"]
595
595
  )
596
- sendStats(
596
+ sendDeviceEvent(
597
597
  action: .downloadError,
598
598
  bundleVersion: version,
599
+ runtimeVersion: runtimeVersion,
599
600
  channel: channel,
600
601
  releaseId: releaseId,
601
- errorMessage: "insufficient_disk_space"
602
+ detail: "insufficient_disk_space"
602
603
  )
603
604
  throw error
604
605
  }
@@ -659,9 +660,10 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
659
660
  cleanupSupersededStagedBundle(previousStagedId: previousStagedId, replacementId: bundleId)
660
661
 
661
662
  notifyListeners("downloadComplete", data: info.toDictionary())
662
- sendStats(
663
+ sendDeviceEvent(
663
664
  action: .downloaded,
664
665
  bundleVersion: version,
666
+ runtimeVersion: runtimeVersion,
665
667
  channel: channel,
666
668
  releaseId: releaseId
667
669
  )
@@ -671,12 +673,13 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
671
673
  "version": version,
672
674
  "error": error.localizedDescription,
673
675
  ])
674
- sendStats(
676
+ sendDeviceEvent(
675
677
  action: .downloadError,
676
678
  bundleVersion: version,
679
+ runtimeVersion: runtimeVersion,
677
680
  channel: channel,
678
681
  releaseId: releaseId,
679
- errorMessage: error.localizedDescription
682
+ detail: error.localizedDescription
680
683
  )
681
684
  throw error
682
685
  }
@@ -808,12 +811,13 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
808
811
  store.setFailedBundle(failed)
809
812
  store.setStagedBundleId(nil)
810
813
 
811
- sendStats(
814
+ sendDeviceEvent(
812
815
  action: .rollback,
813
816
  bundleVersion: current.version,
817
+ runtimeVersion: current.runtimeVersion,
814
818
  channel: current.channel,
815
819
  releaseId: current.releaseId,
816
- errorMessage: reason
820
+ detail: reason
817
821
  )
818
822
 
819
823
  let fallback = store.getFallbackBundle()
@@ -906,9 +910,7 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
906
910
  if let runtimeVersion = latest.runtimeVersion {
907
911
  payload["runtimeVersion"] = runtimeVersion
908
912
  }
909
- if let releaseId = latest.releaseId {
910
- payload["releaseId"] = releaseId
911
- }
913
+ payload["releaseId"] = latest.releaseId
912
914
  return payload
913
915
  }
914
916
 
@@ -932,9 +934,8 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
932
934
  return false
933
935
  }
934
936
 
935
- if let latestReleaseId = latest.releaseId,
936
- let bundleReleaseId = bundle.releaseId,
937
- latestReleaseId == bundleReleaseId {
937
+ if let bundleReleaseId = bundle.releaseId,
938
+ latest.releaseId == bundleReleaseId {
938
939
  return true
939
940
  }
940
941
 
@@ -1023,26 +1024,41 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1023
1024
  return "\(normalized)-\(suffix)"
1024
1025
  }
1025
1026
 
1026
- private func sendStats(
1027
- action: StatsAction,
1027
+ private func sendDeviceEvent(
1028
+ action: DeviceEventAction,
1028
1029
  bundleVersion: String? = nil,
1030
+ runtimeVersion: String? = nil,
1029
1031
  channel: String? = nil,
1030
1032
  releaseId: String? = nil,
1031
- errorMessage: String? = nil
1033
+ detail: String? = nil
1032
1034
  ) {
1033
1035
  guard let appId else {
1034
1036
  return
1035
1037
  }
1036
- StatsClient.send(
1037
- updateUrl: updateUrl,
1038
+ guard let bundleVersion = trimToNil(bundleVersion) else {
1039
+ print("[UpdateKit] Skipping device event without bundleVersion")
1040
+ return
1041
+ }
1042
+ guard let releaseId = trimToNil(releaseId) else {
1043
+ print("[UpdateKit] Skipping device event without releaseId")
1044
+ return
1045
+ }
1046
+ let nativeBuild = store.nativeBuild.trimmingCharacters(in: .whitespacesAndNewlines)
1047
+ guard !nativeBuild.isEmpty else {
1048
+ print("[UpdateKit] Skipping device event without nativeBuild")
1049
+ return
1050
+ }
1051
+ DeviceEventClient.send(
1052
+ ingestUrl: ingestUrl,
1038
1053
  appId: appId,
1039
1054
  platform: "ios",
1040
1055
  action: action,
1041
1056
  bundleVersion: bundleVersion,
1042
1057
  channel: channel,
1058
+ runtimeVersion: trimToNil(runtimeVersion),
1043
1059
  releaseId: releaseId,
1044
- nativeBuild: store.nativeBuild,
1045
- errorMessage: errorMessage
1060
+ nativeBuild: nativeBuild,
1061
+ detail: detail
1046
1062
  )
1047
1063
  }
1048
1064
 
@@ -1061,21 +1077,27 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1061
1077
  return trimmed.isEmpty ? nil : trimmed
1062
1078
  }
1063
1079
 
1064
- private func resolveUpdateUrl(configured: String?, env: String?) -> String {
1080
+ private func resolveIngestUrl(
1081
+ configured: String?,
1082
+ env: String?
1083
+ ) -> String {
1065
1084
  let configuredValue = configured?.trimmingCharacters(in: .whitespacesAndNewlines)
1066
1085
  if let configuredValue, !configuredValue.isEmpty {
1067
- return normalizeUpdateUrl(configuredValue)
1086
+ return normalizeIngestUrl(configuredValue)
1068
1087
  }
1069
1088
 
1070
1089
  let envValue = env?.trimmingCharacters(in: .whitespacesAndNewlines)
1071
1090
  if let envValue, !envValue.isEmpty {
1072
- return normalizeUpdateUrl(envValue)
1091
+ return normalizeIngestUrl(envValue)
1073
1092
  }
1074
1093
 
1075
- return UpdaterPlugin.defaultUpdateURL
1094
+ return UpdaterPlugin.defaultIngestURL
1076
1095
  }
1077
1096
 
1078
- private func resolveCdnUrl(configured: String?, env: String?, resolvedUpdateUrl: String) -> String {
1097
+ private func resolveCdnUrl(
1098
+ configured: String?,
1099
+ env: String?
1100
+ ) -> String {
1079
1101
  let configuredValue = configured?.trimmingCharacters(in: .whitespacesAndNewlines)
1080
1102
  if let configuredValue, !configuredValue.isEmpty {
1081
1103
  return normalizeCdnUrl(configuredValue)
@@ -1086,15 +1108,10 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1086
1108
  return normalizeCdnUrl(envValue)
1087
1109
  }
1088
1110
 
1089
- if resolvedUpdateUrl.lowercased() != UpdaterPlugin.defaultUpdateURL.lowercased(),
1090
- resolvedUpdateUrl.lowercased().hasSuffix(UpdaterPlugin.apiPathSuffix) {
1091
- return String(resolvedUpdateUrl.dropLast(UpdaterPlugin.apiPathSuffix.count))
1092
- }
1093
-
1094
1111
  return UpdaterPlugin.defaultCdnURL
1095
1112
  }
1096
1113
 
1097
- private func normalizeUpdateUrl(_ raw: String) -> String {
1114
+ private func normalizeIngestUrl(_ raw: String) -> String {
1098
1115
  let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
1099
1116
  let withoutTrailingSlash = trimmed.replacingOccurrences(
1100
1117
  of: "/+$",
@@ -1102,11 +1119,11 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1102
1119
  options: .regularExpression
1103
1120
  )
1104
1121
 
1105
- if withoutTrailingSlash.lowercased().hasSuffix(UpdaterPlugin.apiPathSuffix) {
1122
+ if withoutTrailingSlash.lowercased().hasSuffix(UpdaterPlugin.ingestPathSuffix) {
1106
1123
  return withoutTrailingSlash
1107
1124
  }
1108
1125
 
1109
- return withoutTrailingSlash + UpdaterPlugin.apiPathSuffix
1126
+ return withoutTrailingSlash + UpdaterPlugin.ingestPathSuffix
1110
1127
  }
1111
1128
 
1112
1129
  private func normalizeCdnUrl(_ raw: String) -> String {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otakit/capacitor-updater",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Capacitor plugin for OTA updates",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",