@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 +4 -6
- package/android/src/main/java/com/updatekit/updater/{StatsClient.java → DeviceEventClient.java} +30 -19
- package/android/src/main/java/com/updatekit/updater/ManifestClient.java +7 -1
- package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +67 -40
- package/dist/esm/definitions.d.ts +5 -3
- package/dist/esm/definitions.d.ts.map +1 -1
- package/ios/Sources/UpdaterPlugin/{StatsClient.swift → DeviceEventClient.swift} +21 -21
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +6 -2
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +60 -43
- package/package.json +1 -1
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).
|
package/android/src/main/java/com/updatekit/updater/{StatsClient.java → DeviceEventClient.java}
RENAMED
|
@@ -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
|
|
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
|
}
|
|
@@ -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
|
|
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"));
|
|
@@ -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 {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1064
|
+
private String resolveIngestUrl(String configured, String env) {
|
|
1047
1065
|
String configuredValue = trimToNull(configured);
|
|
1048
1066
|
if (configuredValue != null) {
|
|
1049
|
-
return
|
|
1067
|
+
return normalizeIngestUrl(configuredValue);
|
|
1050
1068
|
}
|
|
1051
1069
|
|
|
1052
1070
|
String envValue = trimToNull(env);
|
|
1053
1071
|
if (envValue != null) {
|
|
1054
|
-
return
|
|
1072
|
+
return normalizeIngestUrl(envValue);
|
|
1055
1073
|
}
|
|
1056
1074
|
|
|
1057
|
-
return
|
|
1075
|
+
return DEFAULT_INGEST_URL;
|
|
1058
1076
|
}
|
|
1059
1077
|
|
|
1060
|
-
private String resolveCdnUrl(String configured, String env
|
|
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
|
|
1092
|
+
private String normalizeIngestUrl(String raw) {
|
|
1083
1093
|
String trimmed = raw.trim().replaceAll("/+$", "");
|
|
1084
|
-
if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(
|
|
1094
|
+
if (trimmed.toLowerCase(java.util.Locale.ROOT).endsWith(INGEST_PATH_SUFFIX)) {
|
|
1085
1095
|
return trimmed;
|
|
1086
1096
|
}
|
|
1087
|
-
return trimmed +
|
|
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
|
|
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
|
|
1124
|
+
String detail
|
|
1114
1125
|
) {
|
|
1115
1126
|
if (appId == null) {
|
|
1116
1127
|
return;
|
|
1117
1128
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1149
|
+
normalizedBundleVersion,
|
|
1124
1150
|
channel,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
|
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
|
|
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
|
|
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":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D;;GAEG;AACH,oBAAY,YAAY;IACtB,+BAA+B;IAC/B,OAAO,YAAY;IACnB,iDAAiD;IACjD,OAAO,YAAY;IACnB,mCAAmC;IACnC,KAAK,UAAU;IACf,wBAAwB;IACxB,OAAO,YAAY;IACnB,yDAAyD;IACzD,KAAK,UAAU;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uDAAuD;IACvD,SAAS,
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D;;GAEG;AACH,oBAAY,YAAY;IACtB,+BAA+B;IAC/B,OAAO,YAAY;IACnB,iDAAiD;IACjD,OAAO,YAAY;IACnB,mCAAmC;IACnC,KAAK,UAAU;IACf,wBAAwB;IACxB,OAAO,YAAY;IACnB,yDAAyD;IACzD,KAAK,UAAU;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,WAAW,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uGAAuG;IACvG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yHAAyH;IACzH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEzC;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,MAAM,MAAM,WAAW,GACnB,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,mBAAmB,GACnB,UAAU,GACV,UAAU,CAAC;AAEf,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAEjC;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAEvC;;;;OAIG;IACH,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEvC;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;;;;;OAQG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;;;;;;OAQG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,KAAK,EAAE,cAAc,CAAC;IAEtB;;OAEG;IACH,WAAW,CACT,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC5C,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC,WAAW,CACT,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC,WAAW,CACT,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC3D,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC,WAAW,CACT,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC,WAAW,CAAC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAE7F,WAAW,CACT,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC,WAAW,CACT,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC9E,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;OAEG;IACH,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACvC,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9C,iBAAiB,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,mBAAmB,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAClD,WAAW,CACT,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC5C,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CACT,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CACT,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC3D,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CACT,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CAAC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC7F,WAAW,CACT,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CACT,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC9E,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
-
enum
|
|
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
|
|
10
|
+
enum DeviceEventClient {
|
|
11
11
|
static func send(
|
|
12
|
-
|
|
12
|
+
ingestUrl: String,
|
|
13
13
|
appId: String,
|
|
14
14
|
platform: String,
|
|
15
|
-
action:
|
|
16
|
-
bundleVersion: String
|
|
15
|
+
action: DeviceEventAction,
|
|
16
|
+
bundleVersion: String,
|
|
17
17
|
channel: String?,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
runtimeVersion: String?,
|
|
19
|
+
releaseId: String,
|
|
20
|
+
nativeBuild: String,
|
|
21
|
+
detail: String?
|
|
21
22
|
) {
|
|
22
|
-
let sanitizedBase =
|
|
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)/
|
|
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
|
|
43
|
-
payload["
|
|
44
|
-
}
|
|
45
|
-
if let nativeBuild {
|
|
46
|
-
payload["nativeBuild"] = nativeBuild
|
|
45
|
+
if let runtimeVersion, !runtimeVersion.isEmpty {
|
|
46
|
+
payload["runtimeVersion"] = runtimeVersion
|
|
47
47
|
}
|
|
48
|
-
if let
|
|
49
|
-
payload["
|
|
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
|
-
//
|
|
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
|
|
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
|
|
52
|
+
private static let defaultIngestURL = "https://ingest.otakit.app/v1"
|
|
53
53
|
private static let defaultCdnURL = "https://cdn.otakit.app"
|
|
54
|
-
private static let
|
|
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
|
|
58
|
+
let envIngestUrl = ProcessInfo.processInfo.environment["OTAKIT_INGEST_URL"]
|
|
59
59
|
let envCdnUrl = ProcessInfo.processInfo.environment["OTAKIT_CDN_URL"]
|
|
60
|
-
|
|
61
|
-
configured: getConfig().getString("
|
|
62
|
-
env:
|
|
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
|
-
|
|
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
|
-
|
|
596
|
+
sendDeviceEvent(
|
|
597
597
|
action: .downloadError,
|
|
598
598
|
bundleVersion: version,
|
|
599
|
+
runtimeVersion: runtimeVersion,
|
|
599
600
|
channel: channel,
|
|
600
601
|
releaseId: releaseId,
|
|
601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
676
|
+
sendDeviceEvent(
|
|
675
677
|
action: .downloadError,
|
|
676
678
|
bundleVersion: version,
|
|
679
|
+
runtimeVersion: runtimeVersion,
|
|
677
680
|
channel: channel,
|
|
678
681
|
releaseId: releaseId,
|
|
679
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
936
|
-
|
|
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
|
|
1027
|
-
action:
|
|
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
|
-
|
|
1033
|
+
detail: String? = nil
|
|
1032
1034
|
) {
|
|
1033
1035
|
guard let appId else {
|
|
1034
1036
|
return
|
|
1035
1037
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
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:
|
|
1045
|
-
|
|
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
|
|
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
|
|
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
|
|
1091
|
+
return normalizeIngestUrl(envValue)
|
|
1073
1092
|
}
|
|
1074
1093
|
|
|
1075
|
-
return UpdaterPlugin.
|
|
1094
|
+
return UpdaterPlugin.defaultIngestURL
|
|
1076
1095
|
}
|
|
1077
1096
|
|
|
1078
|
-
private func resolveCdnUrl(
|
|
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
|
|
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.
|
|
1122
|
+
if withoutTrailingSlash.lowercased().hasSuffix(UpdaterPlugin.ingestPathSuffix) {
|
|
1106
1123
|
return withoutTrailingSlash
|
|
1107
1124
|
}
|
|
1108
1125
|
|
|
1109
|
-
return withoutTrailingSlash + UpdaterPlugin.
|
|
1126
|
+
return withoutTrailingSlash + UpdaterPlugin.ingestPathSuffix
|
|
1110
1127
|
}
|
|
1111
1128
|
|
|
1112
1129
|
private func normalizeCdnUrl(_ raw: String) -> String {
|