@onekeyfe/react-native-bundle-update 1.1.42 → 1.1.44

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.
@@ -248,6 +248,101 @@ object BundleUpdateStoreAndroid {
248
248
  }
249
249
  }
250
250
 
251
+ // Pre-launch pending task processing
252
+
253
+ @Volatile
254
+ private var didProcessPreLaunchTask = false
255
+
256
+ /**
257
+ * Checks MMKV for a pending bundle-switch task and applies it before JS runtime starts.
258
+ * Only handles the happy path (status=pending, bundle exists, env matches).
259
+ * All complex logic (retry, download, error handling) remains in JS layer.
260
+ */
261
+ fun processPreLaunchPendingTask(context: Context) {
262
+ if (didProcessPreLaunchTask) return
263
+ didProcessPreLaunchTask = true
264
+ try {
265
+ // 1. Read MMKV
266
+ MMKV.initialize(context)
267
+ val mmkv = MMKV.mmkvWithID("onekey-app-setting") ?: return
268
+ val taskJson = mmkv.decodeString("onekey_pending_install_task") ?: return
269
+ val taskObj = JSONObject(taskJson)
270
+
271
+ // 2. Validate task fields
272
+ if (taskObj.optString("status") != "pending") return
273
+ if (taskObj.optString("action") != "switch-bundle") return
274
+ if (taskObj.optString("type") != "jsbundle-switch") return
275
+
276
+ val now = System.currentTimeMillis()
277
+ if (taskObj.optLong("expiresAt", 0) <= now) return
278
+ val nextRetryAt = taskObj.optLong("nextRetryAt", 0)
279
+ if (nextRetryAt > 0 && nextRetryAt > now) return
280
+
281
+ // 3. Verify scheduledEnv matches current state
282
+ val currentAppVersion = getAppVersion(context) ?: return
283
+ if (taskObj.optString("scheduledEnvAppVersion") != currentAppVersion) return
284
+
285
+ val currentBV = getCurrentBundleVersion(context)
286
+ val currentBundleVersionStr = if (currentBV != null) {
287
+ val idx = currentBV.lastIndexOf("-")
288
+ if (idx > 0) currentBV.substring(idx + 1) else getBuiltinBundleVersion(context)
289
+ } else {
290
+ getBuiltinBundleVersion(context)
291
+ }
292
+ if (taskObj.optString("scheduledEnvBundleVersion") != currentBundleVersionStr) return
293
+
294
+ // 4. Extract payload
295
+ val payload = taskObj.optJSONObject("payload") ?: return
296
+ val appVersion = payload.optString("appVersion")
297
+ val bundleVersion = payload.optString("bundleVersion")
298
+ val signature = payload.optString("signature")
299
+ if (appVersion.isEmpty() || bundleVersion.isEmpty() || signature.isEmpty()) return
300
+ if (!isSafeVersionString(appVersion) || !isSafeVersionString(bundleVersion)) return
301
+
302
+ // 5. Verify bundle directory and entry file exist
303
+ val folderName = "$appVersion-$bundleVersion"
304
+ val bundleDirPath = File(getBundleDir(context), folderName)
305
+ if (!bundleDirPath.exists()) return
306
+ val entryFile = File(bundleDirPath, "main.jsbundle.hbc")
307
+ if (!entryFile.exists()) {
308
+ OneKeyLog.warn("BundleUpdate", "processPreLaunchPendingTask: bundle dir exists but entry file missing: ${entryFile.absolutePath}")
309
+ return
310
+ }
311
+
312
+ // 6. Apply (same as installBundle) — use commit() for synchronous writes
313
+ // to ensure all prefs are persisted atomically before proceeding.
314
+ val bundlePrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
315
+ val versionPrefs = context.getSharedPreferences(NATIVE_VERSION_PREFS_NAME, Context.MODE_PRIVATE)
316
+ val currentVersion = bundlePrefs.getString(CURRENT_BUNDLE_VERSION_KEY, "")
317
+ bundlePrefs.edit()
318
+ .putString(CURRENT_BUNDLE_VERSION_KEY, folderName)
319
+ .apply {
320
+ if (!currentVersion.isNullOrEmpty()) {
321
+ remove(currentVersion) // legacy signature key cleanup
322
+ }
323
+ }
324
+ .commit()
325
+ if (!signature.isNullOrEmpty()) {
326
+ writeSignatureFile(context, folderName, signature)
327
+ }
328
+ versionPrefs.edit()
329
+ .putString("nativeVersion", currentAppVersion)
330
+ .putString("nativeBuildNumber", getBuildNumber(context))
331
+ .commit()
332
+
333
+ // 7. Update MMKV task status → applied_waiting_verify
334
+ // Do NOT set runningStartedAt — a falsy value lets JS skip the
335
+ // 10-minute grace period and verify alignment immediately on boot.
336
+ taskObj.put("status", "applied_waiting_verify")
337
+ taskObj.remove("runningStartedAt")
338
+ mmkv.encode("onekey_pending_install_task", taskObj.toString())
339
+
340
+ OneKeyLog.info("BundleUpdate", "processPreLaunchPendingTask: switched to $folderName")
341
+ } catch (e: Exception) {
342
+ OneKeyLog.error("BundleUpdate", "processPreLaunchPendingTask failed: ${e.message}")
343
+ }
344
+ }
345
+
251
346
  fun calculateSHA256(filePath: String): String? {
252
347
  return try {
253
348
  val digest = MessageDigest.getInstance("SHA-256")
@@ -536,6 +631,7 @@ object BundleUpdateStoreAndroid {
536
631
  }
537
632
 
538
633
  fun getCurrentBundleMainJSBundle(context: Context): String? {
634
+ processPreLaunchPendingTask(context)
539
635
  return try {
540
636
  val currentAppVersion = getAppVersion(context)
541
637
  val currentBundleVersion = getCurrentBundleVersion(context) ?: run {
@@ -202,6 +202,99 @@ public class BundleUpdateStore: NSObject {
202
202
  Bundle.main.infoDictionary?["BUNDLE_VERSION"] as? String ?? ""
203
203
  }
204
204
 
205
+ // MARK: - Pre-launch pending task processing
206
+
207
+ private static var didProcessPreLaunchTask = false
208
+
209
+ /// Checks MMKV for a pending bundle-switch task and applies it before JS runtime starts.
210
+ /// Only handles the happy path (status=pending, bundle exists, env matches).
211
+ /// All complex logic (retry, download, error handling) remains in JS layer.
212
+ public static func processPreLaunchPendingTask() {
213
+ guard !didProcessPreLaunchTask else { return }
214
+ didProcessPreLaunchTask = true
215
+
216
+ do {
217
+ // 1. Read MMKV (same pattern as isDevSettingsEnabled)
218
+ MMKV.initialize(rootDir: nil)
219
+ guard let mmkv = MMKV(mmapID: "onekey-app-setting"),
220
+ let taskJson = mmkv.string(forKey: "onekey_pending_install_task"),
221
+ let data = taskJson.data(using: .utf8),
222
+ let taskDict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
223
+ else { return }
224
+
225
+ // 2. Validate task fields
226
+ guard taskDict["status"] as? String == "pending",
227
+ taskDict["action"] as? String == "switch-bundle",
228
+ taskDict["type"] as? String == "jsbundle-switch"
229
+ else { return }
230
+
231
+ let now = Int64(Date().timeIntervalSince1970 * 1000)
232
+ guard let expiresAtNum = taskDict["expiresAt"] as? NSNumber else { return }
233
+ let expiresAt = expiresAtNum.int64Value
234
+ guard expiresAt > now else { return }
235
+ if let nextRetryAtNum = taskDict["nextRetryAt"] as? NSNumber {
236
+ if nextRetryAtNum.int64Value > now { return }
237
+ }
238
+
239
+ // 3. Verify scheduledEnv matches current state
240
+ let currentAppVersion = getCurrentNativeVersion()
241
+ guard taskDict["scheduledEnvAppVersion"] as? String == currentAppVersion else { return }
242
+
243
+ let currentBundleVersionStr: String
244
+ if let cbv = currentBundleVersion(),
245
+ let dashRange = cbv.range(of: "-", options: .backwards) {
246
+ currentBundleVersionStr = String(cbv[dashRange.upperBound...])
247
+ } else {
248
+ currentBundleVersionStr = getBuiltinBundleVersion()
249
+ }
250
+ guard taskDict["scheduledEnvBundleVersion"] as? String == currentBundleVersionStr else { return }
251
+
252
+ // 4. Extract payload
253
+ guard let payload = taskDict["payload"] as? [String: Any],
254
+ let appVersion = payload["appVersion"] as? String,
255
+ let bundleVersion = payload["bundleVersion"] as? String,
256
+ let signature = payload["signature"] as? String,
257
+ !appVersion.isEmpty, !bundleVersion.isEmpty, !signature.isEmpty,
258
+ appVersion.isSafeVersionString, bundleVersion.isSafeVersionString
259
+ else { return }
260
+
261
+ // 5. Verify bundle directory and entry file exist
262
+ let folderName = "\(appVersion)-\(bundleVersion)"
263
+ let bundleDirPath = (bundleDir() as NSString).appendingPathComponent(folderName)
264
+ guard FileManager.default.fileExists(atPath: bundleDirPath) else { return }
265
+ let entryFilePath = (bundleDirPath as NSString).appendingPathComponent("main.jsbundle.hbc")
266
+ guard FileManager.default.fileExists(atPath: entryFilePath) else {
267
+ OneKeyLog.warn("BundleUpdate", "processPreLaunchPendingTask: bundle dir exists but entry file missing: \(entryFilePath)")
268
+ return
269
+ }
270
+
271
+ // 6. Apply: set currentBundleVersion (same as installBundle)
272
+ let ud = UserDefaults.standard
273
+ ud.set(folderName, forKey: bundlePrefsKey)
274
+ if !signature.isEmpty {
275
+ writeSignatureFile(folderName, signature: signature)
276
+ }
277
+ setNativeVersion(currentAppVersion)
278
+ setNativeBuildNumber(getCurrentNativeBuildNumber())
279
+ ud.synchronize()
280
+
281
+ // 7. Update MMKV task status → applied_waiting_verify
282
+ // Do NOT set runningStartedAt — a falsy value lets JS skip the
283
+ // 10-minute grace period and verify alignment immediately on boot.
284
+ var updatedTask = taskDict
285
+ updatedTask["status"] = "applied_waiting_verify"
286
+ updatedTask.removeValue(forKey: "runningStartedAt")
287
+ let jsonData = try JSONSerialization.data(withJSONObject: updatedTask)
288
+ if let jsonStr = String(data: jsonData, encoding: .utf8) {
289
+ mmkv.set(jsonStr, forKey: "onekey_pending_install_task")
290
+ }
291
+
292
+ OneKeyLog.info("BundleUpdate", "processPreLaunchPendingTask: switched to \(folderName)")
293
+ } catch {
294
+ OneKeyLog.error("BundleUpdate", "processPreLaunchPendingTask failed: \(error)")
295
+ }
296
+ }
297
+
205
298
  public static func getMetadataFilePath(_ currentBundleVersion: String) -> String? {
206
299
  let path = (bundleDir() as NSString)
207
300
  .appendingPathComponent(currentBundleVersion)
@@ -406,6 +499,7 @@ public class BundleUpdateStore: NSObject {
406
499
  }
407
500
 
408
501
  public static func currentBundleMainJSBundle() -> String? {
502
+ processPreLaunchPendingTask()
409
503
  guard let currentBundleVer = currentBundleVersion() else {
410
504
  OneKeyLog.warn("BundleUpdate", "getJsBundlePath: no currentBundleVersion stored")
411
505
  return nil
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-bundle-update",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "description": "react-native-bundle-update",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",