@otakit/capacitor-updater 1.0.0 → 1.2.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.
@@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
21
21
  import java.security.MessageDigest;
22
22
  import java.util.concurrent.ExecutorService;
23
23
  import java.util.concurrent.Executors;
24
+ import java.util.concurrent.atomic.AtomicBoolean;
24
25
 
25
26
  @CapacitorPlugin(name = "OtaKit")
26
27
  public class UpdaterPlugin extends Plugin {
@@ -38,12 +39,19 @@ public class UpdaterPlugin extends Plugin {
38
39
  private String updateUrl;
39
40
  private String appId;
40
41
  private String channel;
42
+ private String runtimeVersion;
41
43
  private java.util.List<ManifestVerifier.KeyEntry> manifestKeys = new java.util.ArrayList<>();
44
+ private long checkIntervalMs = 600_000;
45
+ private final AtomicBoolean checkInProgress = new AtomicBoolean(false);
42
46
  private static final String DEFAULT_UPDATE_URL = "https://www.otakit.app/api/v1";
43
47
  private static final String API_PATH_SUFFIX = "/api/v1";
44
48
  private static final String UPDATE_MODE_MANUAL = "manual";
45
49
  private static final String UPDATE_MODE_NEXT_LAUNCH = "next-launch";
50
+ private static final String UPDATE_MODE_NEXT_RESUME = "next-resume";
46
51
  private static final String UPDATE_MODE_IMMEDIATE = "immediate";
52
+ private static final String KEY_LAST_CHECK_TIMESTAMP = "last_check_timestamp";
53
+ private static final String TRIGGER_LAUNCH = "launch";
54
+ private static final String TRIGGER_RESUME = "resume";
47
55
 
48
56
  @Override
49
57
  public void load() {
@@ -64,13 +72,14 @@ public class UpdaterPlugin extends Plugin {
64
72
  }
65
73
  } catch (PackageManager.NameNotFoundException ignored) {}
66
74
 
67
- this.store = new BundleStore(getContext(), builtinVersion, nativeBuild);
68
75
  this.updateUrl = resolveUpdateUrl(
69
76
  getConfig().getString("serverUrl"),
70
77
  System.getenv("OTAKIT_SERVER_URL")
71
78
  );
72
79
  this.appId = getConfig().getString("appId");
73
80
  this.channel = trimToNull(getConfig().getString("channel"));
81
+ this.runtimeVersion = trimToNull(getConfig().getString("runtimeVersion"));
82
+ this.store = new BundleStore(getContext(), builtinVersion, nativeBuild, this.runtimeVersion);
74
83
  this.allowInsecureUrls = getConfig().getBoolean("allowInsecureUrls", false);
75
84
  String configuredUpdateMode = getConfig().getString("updateMode", UPDATE_MODE_NEXT_LAUNCH);
76
85
  this.updateMode = resolveUpdateMode(configuredUpdateMode);
@@ -112,6 +121,9 @@ public class UpdaterPlugin extends Plugin {
112
121
  }
113
122
 
114
123
  this.appReadyTimeoutMs = Math.max(1000, getConfig().getInt("appReadyTimeout", 10_000));
124
+ this.checkIntervalMs = Math.max(600_000, getConfig().getInt("checkInterval", 600_000));
125
+
126
+ pruneIncompatibleBundles();
115
127
 
116
128
  BundleInfo current = store.getCurrentBundle();
117
129
  if (current.status == BundleStatus.TRIAL) {
@@ -135,26 +147,84 @@ public class UpdaterPlugin extends Plugin {
135
147
  }
136
148
 
137
149
  if (isAutomaticUpdateMode()) {
138
- startAutoUpdate();
150
+ runAutomaticUpdate(TRIGGER_LAUNCH);
139
151
  }
140
152
  }
141
153
 
142
- private void startAutoUpdate() {
143
- executor.execute(() -> {
144
- try {
145
- BundleInfo downloaded = performCheckAndDownload(null, true);
146
- if (UPDATE_MODE_IMMEDIATE.equals(updateMode) && downloaded != null) {
154
+ @Override
155
+ protected void handleOnResume() {
156
+ super.handleOnResume();
157
+ if (isAutomaticUpdateMode()) {
158
+ runAutomaticUpdate(TRIGGER_RESUME);
159
+ }
160
+ }
161
+
162
+ private void runAutomaticUpdate(String trigger) {
163
+ // Resume-only guards
164
+ if (TRIGGER_RESUME.equals(trigger)) {
165
+ if (UPDATE_MODE_MANUAL.equals(updateMode)) return;
166
+
167
+ // next-resume and immediate: activate staged bundle on resume without server check
168
+ if ((UPDATE_MODE_NEXT_RESUME.equals(updateMode) || UPDATE_MODE_IMMEDIATE.equals(updateMode))) {
169
+ String stagedId = store.getStagedBundleId();
170
+ if (stagedId != null && store.getBundle(stagedId) != null) {
147
171
  activateStagedBundleForReload();
148
172
  reloadWebView();
173
+ return;
149
174
  }
150
- } catch (Exception e) {
151
- if (UPDATE_MODE_IMMEDIATE.equals(updateMode)) {
152
- android.util.Log.w("UpdateKit", "immediate startup update failed", e);
175
+ }
176
+
177
+ if (shouldThrottleCheck()) return;
178
+ }
179
+
180
+ // Acquire in-flight guard (submit-time)
181
+ if (!checkInProgress.compareAndSet(false, true)) return;
182
+
183
+ if (UPDATE_MODE_IMMEDIATE.equals(updateMode)) {
184
+ // Immediate: check+download, activate if found
185
+ executor.execute(() -> {
186
+ try {
187
+ BundleInfo result = performCheckAndDownload(null, true);
188
+ recordCheckTimestamp();
189
+ if (result != null) {
190
+ activateStagedBundleForReload();
191
+ reloadWebView();
192
+ }
193
+ } catch (Exception e) {
194
+ android.util.Log.w("OtaKit", "immediate update failed (" + trigger + ")", e);
195
+ } finally {
196
+ checkInProgress.set(false);
153
197
  }
198
+ });
199
+ return;
200
+ }
201
+
202
+ // next-launch / next-resume: background check+download (fire-and-forget)
203
+ executor.execute(() -> {
204
+ try {
205
+ performCheckAndDownload(null, true);
206
+ recordCheckTimestamp();
207
+ } catch (Exception ignored) {
208
+ // Check failed — timestamp not recorded, will retry on next trigger
209
+ } finally {
210
+ checkInProgress.set(false);
154
211
  }
155
212
  });
156
213
  }
157
214
 
215
+ private boolean shouldThrottleCheck() {
216
+ long lastCheck = store.getPrefs().getLong(KEY_LAST_CHECK_TIMESTAMP, 0);
217
+ if (lastCheck <= 0) return false;
218
+ long elapsed = System.currentTimeMillis() - lastCheck;
219
+ return elapsed < checkIntervalMs;
220
+ }
221
+
222
+ private void recordCheckTimestamp() {
223
+ store.getPrefs().edit()
224
+ .putLong(KEY_LAST_CHECK_TIMESTAMP, System.currentTimeMillis())
225
+ .apply();
226
+ }
227
+
158
228
  private boolean shouldActivateStagedOnLaunch() {
159
229
  return !UPDATE_MODE_MANUAL.equals(updateMode);
160
230
  }
@@ -175,12 +245,15 @@ public class UpdaterPlugin extends Plugin {
175
245
  if (UPDATE_MODE_MANUAL.equals(raw)) {
176
246
  return UPDATE_MODE_MANUAL;
177
247
  }
248
+ if (UPDATE_MODE_NEXT_RESUME.equals(raw)) {
249
+ return UPDATE_MODE_NEXT_RESUME;
250
+ }
178
251
  if (UPDATE_MODE_IMMEDIATE.equals(raw)) {
179
252
  return UPDATE_MODE_IMMEDIATE;
180
253
  }
181
254
 
182
255
  android.util.Log.w(
183
- "UpdateKit",
256
+ "OtaKit",
184
257
  "Unknown updateMode '" + raw + "', defaulting to 'next-launch'"
185
258
  );
186
259
  return UPDATE_MODE_NEXT_LAUNCH;
@@ -209,10 +282,37 @@ public class UpdaterPlugin extends Plugin {
209
282
 
210
283
  @PluginMethod
211
284
  public void check(PluginCall call) {
285
+ // Respect throttle — return staged bundle as a LatestVersion if available, else null
286
+ if (shouldThrottleCheck() || !checkInProgress.compareAndSet(false, true)) {
287
+ String stagedId = store.getStagedBundleId();
288
+ if (stagedId != null) {
289
+ BundleInfo staged = store.getBundle(stagedId);
290
+ if (staged != null) {
291
+ JSObject result = new JSObject();
292
+ result.put("version", staged.version);
293
+ result.put("url", "");
294
+ result.put("sha256", staged.sha256 != null ? staged.sha256 : "");
295
+ result.put("size", 0);
296
+ result.put("downloaded", true);
297
+ if (staged.runtimeVersion != null) {
298
+ result.put("runtimeVersion", staged.runtimeVersion);
299
+ }
300
+ if (staged.releaseId != null) {
301
+ result.put("releaseId", staged.releaseId);
302
+ }
303
+ call.resolve(result);
304
+ return;
305
+ }
306
+ }
307
+ call.resolve((JSObject) null);
308
+ return;
309
+ }
212
310
  String targetChannel = resolveTargetChannel(null);
213
311
  executor.execute(() -> {
214
312
  try {
215
313
  ManifestClient.LatestManifest latest = fetchLatest(targetChannel);
314
+ // check() does NOT record timestamp — only download() and automatic paths do.
315
+ // This keeps check() -> download() working without the throttle blocking download().
216
316
  if (latest == null) {
217
317
  call.resolve((JSObject) null);
218
318
  } else {
@@ -221,15 +321,31 @@ public class UpdaterPlugin extends Plugin {
221
321
  }
222
322
  } catch (Exception e) {
223
323
  call.reject("check failed: " + e.getMessage());
324
+ } finally {
325
+ checkInProgress.set(false);
224
326
  }
225
327
  });
226
328
  }
227
329
 
228
330
  @PluginMethod
229
331
  public void download(PluginCall call) {
332
+ // Respect throttle — return staged info if available, else null
333
+ if (shouldThrottleCheck() || !checkInProgress.compareAndSet(false, true)) {
334
+ String stagedId = store.getStagedBundleId();
335
+ if (stagedId != null) {
336
+ BundleInfo staged = store.getBundle(stagedId);
337
+ if (staged != null) {
338
+ call.resolve(staged.toJSObject());
339
+ return;
340
+ }
341
+ }
342
+ call.resolve((JSObject) null);
343
+ return;
344
+ }
230
345
  executor.execute(() -> {
231
346
  try {
232
347
  BundleInfo bundle = performCheckAndDownload(null, true);
348
+ recordCheckTimestamp();
233
349
  if (bundle == null) {
234
350
  call.resolve((JSObject) null);
235
351
  } else {
@@ -237,6 +353,8 @@ public class UpdaterPlugin extends Plugin {
237
353
  }
238
354
  } catch (Exception e) {
239
355
  call.reject("download failed: " + e.getMessage());
356
+ } finally {
357
+ checkInProgress.set(false);
240
358
  }
241
359
  });
242
360
  }
@@ -402,7 +520,7 @@ public class UpdaterPlugin extends Plugin {
402
520
  channel,
403
521
  current.version,
404
522
  current.releaseId,
405
- store.getNativeBuild(),
523
+ runtimeVersion,
406
524
  "android",
407
525
  allowInsecureUrls,
408
526
  manifestKeys
@@ -419,6 +537,12 @@ public class UpdaterPlugin extends Plugin {
419
537
  return null;
420
538
  }
421
539
 
540
+ if (!isCompatibleRuntime(latest.runtimeVersion)) {
541
+ throw new IllegalStateException(
542
+ "Manifest runtimeVersion does not match the installed app runtime"
543
+ );
544
+ }
545
+
422
546
  BundleInfo staged = findMatchingStagedBundle(latest, targetChannel);
423
547
  if (emitEvents) {
424
548
  notifyListeners("updateAvailable", manifestToJSObject(latest, staged != null));
@@ -434,6 +558,7 @@ public class UpdaterPlugin extends Plugin {
434
558
  latest.version,
435
559
  latest.sha256,
436
560
  latest.size,
561
+ latest.runtimeVersion,
437
562
  targetChannel,
438
563
  latest.releaseId
439
564
  );
@@ -447,6 +572,7 @@ public class UpdaterPlugin extends Plugin {
447
572
  refreshed.version,
448
573
  refreshed.sha256,
449
574
  refreshed.size,
575
+ refreshed.runtimeVersion,
450
576
  targetChannel,
451
577
  refreshed.releaseId
452
578
  );
@@ -469,7 +595,7 @@ public class UpdaterPlugin extends Plugin {
469
595
 
470
596
  private BundleInfo downloadAndStage(URL url, String version, String expectedSha256)
471
597
  throws Exception {
472
- return downloadAndStage(url, version, expectedSha256, 0, null, null);
598
+ return downloadAndStage(url, version, expectedSha256, 0, null, null, null);
473
599
  }
474
600
 
475
601
  private BundleInfo downloadAndStage(
@@ -477,6 +603,7 @@ public class UpdaterPlugin extends Plugin {
477
603
  String version,
478
604
  String expectedSha256,
479
605
  int expectedSize,
606
+ String runtimeVersion,
480
607
  String channel,
481
608
  String releaseId
482
609
  ) throws Exception {
@@ -524,6 +651,7 @@ public class UpdaterPlugin extends Plugin {
524
651
  BundleInfo info = new BundleInfo(
525
652
  bundleId,
526
653
  version,
654
+ runtimeVersion,
527
655
  BundleStatus.PENDING,
528
656
  System.currentTimeMillis(),
529
657
  expectedSha256,
@@ -619,6 +747,12 @@ public class UpdaterPlugin extends Plugin {
619
747
  store.setStagedBundleId(null);
620
748
  return store.getCurrentBundle();
621
749
  }
750
+ if (!isCompatibleRuntime(staged)) {
751
+ try {
752
+ store.deleteBundle(staged.id);
753
+ } catch (Exception ignored) {}
754
+ return store.getCurrentBundle();
755
+ }
622
756
 
623
757
  store.setCurrentBundleId(staged.id);
624
758
  store.setStagedBundleId(null);
@@ -636,6 +770,12 @@ public class UpdaterPlugin extends Plugin {
636
770
  store.setStagedBundleId(null);
637
771
  return;
638
772
  }
773
+ if (!isCompatibleRuntime(staged)) {
774
+ try {
775
+ store.deleteBundle(staged.id);
776
+ } catch (Exception ignored) {}
777
+ return;
778
+ }
639
779
 
640
780
  store.setCurrentBundleId(staged.id);
641
781
  store.setStagedBundleId(null);
@@ -763,12 +903,12 @@ public class UpdaterPlugin extends Plugin {
763
903
  object.put("sha256", latest.sha256);
764
904
  object.put("size", latest.size);
765
905
  object.put("downloaded", downloaded);
906
+ if (latest.runtimeVersion != null) {
907
+ object.put("runtimeVersion", latest.runtimeVersion);
908
+ }
766
909
  if (latest.releaseId != null) {
767
910
  object.put("releaseId", latest.releaseId);
768
911
  }
769
- if (latest.minNativeBuild != null) {
770
- object.put("minNativeBuild", latest.minNativeBuild);
771
- }
772
912
  return object;
773
913
  }
774
914
 
@@ -791,6 +931,10 @@ public class UpdaterPlugin extends Plugin {
791
931
  return null;
792
932
  }
793
933
 
934
+ if (!java.util.Objects.equals(trimToNull(staged.runtimeVersion), trimToNull(latest.runtimeVersion))) {
935
+ return null;
936
+ }
937
+
794
938
  if (
795
939
  latest.releaseId != null &&
796
940
  staged.releaseId != null &&
@@ -950,6 +1094,29 @@ public class UpdaterPlugin extends Plugin {
950
1094
  );
951
1095
  }
952
1096
 
1097
+ private void pruneIncompatibleBundles() {
1098
+ for (BundleInfo bundle : store.listDownloadedBundleInfos()) {
1099
+ if (!isCompatibleRuntime(bundle)) {
1100
+ try {
1101
+ store.deleteBundle(bundle.id);
1102
+ } catch (Exception ignored) {}
1103
+ }
1104
+ }
1105
+
1106
+ BundleInfo failed = store.getFailedBundle();
1107
+ if (failed != null && !isCompatibleRuntime(failed)) {
1108
+ store.setFailedBundle(null);
1109
+ }
1110
+ }
1111
+
1112
+ private boolean isCompatibleRuntime(String bundleRuntimeVersion) {
1113
+ return java.util.Objects.equals(trimToNull(bundleRuntimeVersion), runtimeVersion);
1114
+ }
1115
+
1116
+ private boolean isCompatibleRuntime(BundleInfo bundle) {
1117
+ return isCompatibleRuntime(bundle.runtimeVersion);
1118
+ }
1119
+
953
1120
  private long getFreeDiskSpace() {
954
1121
  try {
955
1122
  android.os.StatFs statFs = new android.os.StatFs(
@@ -19,6 +19,8 @@ export interface BundleInfo {
19
19
  id: string;
20
20
  /** Semantic version string */
21
21
  version: string;
22
+ /** Native compatibility lane for this bundle. */
23
+ runtimeVersion?: string;
22
24
  /** Current status of the bundle */
23
25
  status: BundleStatus;
24
26
  /** ISO timestamp when bundle was downloaded */
@@ -33,6 +35,8 @@ export interface BundleInfo {
33
35
  export interface LatestVersion {
34
36
  /** Version string */
35
37
  version: string;
38
+ /** Native compatibility lane for this update. */
39
+ runtimeVersion?: string;
36
40
  /** Download URL */
37
41
  url: string;
38
42
  /** SHA-256 checksum */
@@ -43,8 +47,6 @@ export interface LatestVersion {
43
47
  downloaded?: boolean;
44
48
  /** Release history ID associated with this manifest */
45
49
  releaseId?: string;
46
- /** Minimum native build required */
47
- minNativeBuild?: number;
48
50
  }
49
51
  export interface BundleListResult {
50
52
  bundles: BundleInfo[];
@@ -57,7 +59,7 @@ export interface OtaKitState {
57
59
  export interface OtaKitDebugState extends OtaKitState {
58
60
  fallback: BundleInfo;
59
61
  }
60
- export type OtaKitUpdateMode = 'manual' | 'next-launch' | 'immediate';
62
+ export type OtaKitUpdateMode = 'manual' | 'next-launch' | 'next-resume' | 'immediate';
61
63
  export interface OtaKitManifestKey {
62
64
  kid: string;
63
65
  key: string;
@@ -70,8 +72,16 @@ export interface OtaKitConfig {
70
72
  appId: string;
71
73
  /** Optional named release track. Omit to use the base channel. */
72
74
  channel?: string;
75
+ /** Optional native compatibility lane. Set this when a new store build should start a new OTA line. */
76
+ runtimeVersion?: string;
73
77
  /** Overall update behavior. Defaults to next-launch. */
74
78
  updateMode?: OtaKitUpdateMode;
79
+ /**
80
+ * Minimum milliseconds between automatic update checks.
81
+ * Applies to both resume-triggered and manual check()/download() calls.
82
+ * Debug APIs bypass this throttle. Defaults to 600000 (10 min).
83
+ */
84
+ checkInterval?: number;
75
85
  /** Milliseconds to wait for notifyAppReady(). Defaults to 10000. */
76
86
  appReadyTimeout?: number;
77
87
  /** Custom API base URL for self-hosted or custom servers. */
@@ -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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"}
@@ -3,6 +3,7 @@ import Foundation
3
3
  struct BundleInfo: Codable {
4
4
  let id: String
5
5
  let version: String
6
+ let runtimeVersion: String?
6
7
  let status: BundleStatus
7
8
  let downloadedAt: Date?
8
9
  let sha256: String?
@@ -21,6 +22,10 @@ struct BundleInfo: Codable {
21
22
  "status": status.rawValue,
22
23
  ]
23
24
 
25
+ if let runtimeVersion {
26
+ result["runtimeVersion"] = runtimeVersion
27
+ }
28
+
24
29
  if let downloadedAt {
25
30
  result["downloadedAt"] = ISO8601DateFormatter().string(from: downloadedAt)
26
31
  }
@@ -10,6 +10,7 @@ final class BundleStore {
10
10
 
11
11
  private let defaults = UserDefaults.standard
12
12
  private let fileManager = FileManager.default
13
+ var appRuntimeVersion: String?
13
14
 
14
15
  private lazy var decoder: JSONDecoder = {
15
16
  let decoder = JSONDecoder()
@@ -51,6 +52,7 @@ final class BundleStore {
51
52
  BundleInfo(
52
53
  id: "builtin",
53
54
  version: builtinVersion,
55
+ runtimeVersion: appRuntimeVersion,
54
56
  status: .builtin,
55
57
  downloadedAt: nil,
56
58
  sha256: nil,
@@ -202,6 +204,7 @@ final class BundleStore {
202
204
  bundle = BundleInfo(
203
205
  id: bundle.id,
204
206
  version: bundle.version,
207
+ runtimeVersion: bundle.runtimeVersion,
205
208
  status: status,
206
209
  downloadedAt: bundle.downloadedAt,
207
210
  sha256: bundle.sha256,
@@ -5,9 +5,8 @@ struct LatestManifest {
5
5
  let url: String
6
6
  let sha256: String
7
7
  let size: Int
8
- let minNativeBuild: Int?
8
+ let runtimeVersion: String?
9
9
  let releaseId: String?
10
- let signature: ManifestSignature?
11
10
  }
12
11
 
13
12
  struct ManifestSignature {
@@ -44,7 +43,7 @@ enum ManifestClient {
44
43
  channel: String?,
45
44
  currentVersion: String,
46
45
  currentReleaseId: String?,
47
- nativeBuild: String,
46
+ runtimeVersion: String?,
48
47
  platform: String,
49
48
  allowInsecureUrls: Bool = false,
50
49
  manifestKeys: [ManifestKey] = []
@@ -73,7 +72,9 @@ enum ManifestClient {
73
72
  if let currentReleaseId, !currentReleaseId.isEmpty {
74
73
  request.setValue(currentReleaseId, forHTTPHeaderField: "X-Release-Id")
75
74
  }
76
- request.setValue(nativeBuild, forHTTPHeaderField: "X-Native-Build")
75
+ if let runtimeVersion, !runtimeVersion.isEmpty {
76
+ request.setValue(runtimeVersion, forHTTPHeaderField: "X-Runtime-Version")
77
+ }
77
78
  request.timeoutInterval = 30
78
79
 
79
80
  let (data, response) = try await URLSession.shared.data(for: request)
@@ -102,21 +103,12 @@ enum ManifestClient {
102
103
  throw ManifestClientError.invalidResponse
103
104
  }
104
105
 
105
- var minNativeBuild: Int?
106
- if let numeric = object["minNativeBuild"] as? NSNumber {
107
- minNativeBuild = numeric.intValue
108
- } else if let stringValue = object["minNativeBuild"] as? String {
109
- minNativeBuild = Int(stringValue)
110
- }
106
+ let runtimeVersion = (object["runtimeVersion"] as? String)?
107
+ .trimmingCharacters(in: .whitespacesAndNewlines)
108
+ .nilIfEmpty
111
109
 
112
- var signature: ManifestSignature?
113
- if let sigObj = object["signature"] as? [String: Any],
114
- let kid = sigObj["kid"] as? String,
115
- let sig = sigObj["sig"] as? String,
116
- let iat = sigObj["iat"] as? Int,
117
- let exp = sigObj["exp"] as? Int {
118
- signature = ManifestSignature(kid: kid, sig: sig, iat: iat, exp: exp)
119
- }
110
+ let signature = parseSignature(object["signature"])
111
+ let signatureV2 = parseSignature(object["signatureV2"])
120
112
 
121
113
  let releaseId = object["releaseId"] as? String
122
114
 
@@ -130,22 +122,36 @@ enum ManifestClient {
130
122
  print("[UpdateKit] WARNING: No manifest signing keys configured — signature verification is disabled for this request.")
131
123
  }
132
124
 
133
- // Verify manifest signature if signing keys are configured
125
+ // Verify manifest signature if signing keys are configured.
126
+ // New manifests carry signatureV2 with runtimeVersion in the signed payload.
127
+ // Older servers only return the legacy signature field.
134
128
  if !manifestKeys.isEmpty {
135
- guard let sig = signature else {
129
+ if let sigV2 = signatureV2 {
130
+ try ManifestVerifier.verify(
131
+ appId: appId,
132
+ channel: channel,
133
+ platform: platform,
134
+ version: version,
135
+ sha256: sha256,
136
+ size: size,
137
+ runtimeVersion: runtimeVersion,
138
+ signature: sigV2,
139
+ trustedKeys: manifestKeys
140
+ )
141
+ } else if let sig = signature {
142
+ try ManifestVerifier.verifyLegacy(
143
+ appId: appId,
144
+ channel: channel,
145
+ platform: platform,
146
+ version: version,
147
+ sha256: sha256,
148
+ size: size,
149
+ signature: sig,
150
+ trustedKeys: manifestKeys
151
+ )
152
+ } else {
136
153
  throw ManifestVerifierError.missingSignature
137
154
  }
138
- try ManifestVerifier.verify(
139
- appId: appId,
140
- channel: channel,
141
- platform: platform,
142
- version: version,
143
- sha256: sha256,
144
- size: size,
145
- minNativeBuild: minNativeBuild,
146
- signature: sig,
147
- trustedKeys: manifestKeys
148
- )
149
155
  }
150
156
 
151
157
  return LatestManifest(
@@ -153,9 +159,26 @@ enum ManifestClient {
153
159
  url: downloadUrl,
154
160
  sha256: sha256,
155
161
  size: size,
156
- minNativeBuild: minNativeBuild,
157
- releaseId: releaseId,
158
- signature: signature
162
+ runtimeVersion: runtimeVersion,
163
+ releaseId: releaseId
159
164
  )
160
165
  }
166
+
167
+ private static func parseSignature(_ rawValue: Any?) -> ManifestSignature? {
168
+ guard let sigObj = rawValue as? [String: Any],
169
+ let kid = sigObj["kid"] as? String,
170
+ let sig = sigObj["sig"] as? String,
171
+ let iat = sigObj["iat"] as? Int,
172
+ let exp = sigObj["exp"] as? Int else {
173
+ return nil
174
+ }
175
+
176
+ return ManifestSignature(kid: kid, sig: sig, iat: iat, exp: exp)
177
+ }
178
+ }
179
+
180
+ private extension String {
181
+ var nilIfEmpty: String? {
182
+ isEmpty ? nil : self
183
+ }
161
184
  }