@hot-updater/react-native 0.35.5 → 0.35.7

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.
@@ -14,4 +14,11 @@
14
14
 
15
15
  -keepclassmembers class * implements com.facebook.react.runtime.ReactHostDelegate {
16
16
  ** jsBundleLoader;
17
- }
17
+ }
18
+
19
+ # Preserve the Brotli decoder and its embedded static dictionary
20
+ # (DictionaryData). R8 otherwise strips the dictionary's static init, causing OTA
21
+ # .tar.br extraction to fail at runtime with:
22
+ # java.io.IOException: Brotli stream decoding failed
23
+ # Caused by: brotli dictionary is not set
24
+ -keep class com.hotupdater.vendor.brotli.** { *; }
@@ -1211,15 +1211,17 @@ class BundleFileStorageService(
1211
1211
 
1212
1212
  if (hasManifestDrivenArtifacts && canUseManifestDrivenInstall()) {
1213
1213
  try {
1214
- updateBundleFromManifest(
1215
- bundleId = bundleId,
1216
- manifestUrl = manifestUrl!!,
1217
- manifestFileHash = manifestFileHash!!,
1218
- changedAssets = changedAssets!!,
1219
- bundleStoreDir = bundleStoreDir,
1220
- finalBundleDir = finalBundleDir,
1221
- progressCallback = progressCallback,
1222
- )
1214
+ withContext(Dispatchers.IO) {
1215
+ updateBundleFromManifest(
1216
+ bundleId = bundleId,
1217
+ manifestUrl = manifestUrl!!,
1218
+ manifestFileHash = manifestFileHash!!,
1219
+ changedAssets = changedAssets!!,
1220
+ bundleStoreDir = bundleStoreDir,
1221
+ finalBundleDir = finalBundleDir,
1222
+ progressCallback = progressCallback,
1223
+ )
1224
+ }
1223
1225
  return
1224
1226
  } catch (e: Exception) {
1225
1227
  if (fileUrl.isNullOrEmpty()) {
@@ -1,6 +1,8 @@
1
1
  package com.hotupdater
2
2
 
3
3
  import android.content.ContextWrapper
4
+ import kotlinx.coroutines.asCoroutineDispatcher
5
+ import kotlinx.coroutines.runBlocking
4
6
  import org.json.JSONObject
5
7
  import org.junit.Assert.assertEquals
6
8
  import org.junit.Assert.assertFalse
@@ -12,6 +14,8 @@ import org.junit.Test
12
14
  import org.junit.rules.TemporaryFolder
13
15
  import java.io.File
14
16
  import java.net.URL
17
+ import java.util.concurrent.CopyOnWriteArrayList
18
+ import java.util.concurrent.Executors
15
19
  import java.util.zip.ZipEntry
16
20
  import java.util.zip.ZipOutputStream
17
21
 
@@ -269,6 +273,47 @@ class BundleFileStorageServiceTest {
269
273
  assertFalse(invokeCanUseManifestDrivenInstall(service))
270
274
  }
271
275
 
276
+ @Test
277
+ fun `manifest driven install moves blocking work off caller dispatcher`() {
278
+ val rootDir = temporaryFolder.newFolder("manifest-install-dispatcher")
279
+ val preferences = InMemoryPreferencesService()
280
+ val downloadService = RecordingFailedDownloadService()
281
+ val service = createService(rootDir, preferences, downloadService)
282
+ val activeDir = createBundleDir(rootDir, "active-bundle")
283
+ val activeBundleFile = writeFile(activeDir, "index.android.bundle")
284
+ writeManifest(activeDir, listOf("index.android.bundle"))
285
+
286
+ preferences.setItem("HotUpdaterBundleURL", activeBundleFile.absolutePath)
287
+
288
+ Executors
289
+ .newSingleThreadExecutor { runnable -> Thread(runnable, "manifest-caller") }
290
+ .asCoroutineDispatcher()
291
+ .use { callerDispatcher ->
292
+ runBlocking(callerDispatcher) {
293
+ val result =
294
+ runCatching {
295
+ service.updateBundle(
296
+ bundleId = "target-bundle",
297
+ fileUrl = "https://example.com/bundle.zip",
298
+ fileHash = null,
299
+ manifestUrl = "https://example.com/manifest.json",
300
+ manifestFileHash = "manifest-hash",
301
+ changedAssets = emptyMap(),
302
+ progressCallback = {},
303
+ )
304
+ }
305
+ assertTrue(result.exceptionOrNull() is HotUpdaterException)
306
+ }
307
+ }
308
+
309
+ val manifestCall = downloadService.calls.first()
310
+ assertEquals("https://example.com/manifest.json", manifestCall.first)
311
+ assertFalse(
312
+ "Manifest install ran on the caller dispatcher: ${manifestCall.second}",
313
+ manifestCall.second.contains("manifest-caller"),
314
+ )
315
+ }
316
+
272
317
  @Test
273
318
  fun `zip decompression does not write sibling prefix traversal entries`() {
274
319
  val rootDir = temporaryFolder.newFolder("zip-sibling-prefix")
@@ -293,11 +338,12 @@ class BundleFileStorageServiceTest {
293
338
  private fun createService(
294
339
  rootDir: File,
295
340
  preferences: InMemoryPreferencesService = InMemoryPreferencesService(),
341
+ downloadService: DownloadService = UnusedDownloadService,
296
342
  ): BundleFileStorageService =
297
343
  BundleFileStorageService(
298
344
  ContextWrapper(null),
299
345
  TestFileSystemService(rootDir),
300
- UnusedDownloadService,
346
+ downloadService,
301
347
  DecompressService(),
302
348
  preferences,
303
349
  TEST_ISOLATION_KEY,
@@ -447,6 +493,20 @@ class BundleFileStorageServiceTest {
447
493
  ): DownloadResult = error("downloadFile should not be called in these tests")
448
494
  }
449
495
 
496
+ private class RecordingFailedDownloadService : DownloadService {
497
+ val calls = CopyOnWriteArrayList<Pair<String, String>>()
498
+
499
+ override suspend fun downloadFile(
500
+ fileUrl: URL,
501
+ destination: File,
502
+ fileSizeCallback: ((Long) -> Unit)?,
503
+ progressCallback: (DownloadProgress) -> Unit,
504
+ ): DownloadResult {
505
+ calls += fileUrl.toString() to Thread.currentThread().name
506
+ return DownloadResult.Error(IllegalStateException("expected download failure"))
507
+ }
508
+ }
509
+
450
510
  companion object {
451
511
  private const val TEST_ISOLATION_KEY = "test-isolation-key"
452
512
  }
@@ -4,5 +4,5 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.HOT_UPDATER_SDK_VERSION = void 0;
7
- const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "0.35.5";
7
+ const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "0.35.7";
8
8
  //# sourceMappingURL=sdkVersion.js.map
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
 
3
- export const HOT_UPDATER_SDK_VERSION = "0.35.5";
3
+ export const HOT_UPDATER_SDK_VERSION = "0.35.7";
4
4
  //# sourceMappingURL=sdkVersion.js.map
@@ -1,2 +1,2 @@
1
- export declare const HOT_UPDATER_SDK_VERSION = "0.35.5";
1
+ export declare const HOT_UPDATER_SDK_VERSION = "0.35.7";
2
2
  //# sourceMappingURL=sdkVersion.d.ts.map
@@ -1,2 +1,2 @@
1
- export declare const HOT_UPDATER_SDK_VERSION = "0.35.5";
1
+ export declare const HOT_UPDATER_SDK_VERSION = "0.35.7";
2
2
  //# sourceMappingURL=sdkVersion.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.35.5",
3
+ "version": "0.35.7",
4
4
  "description": "React Native OTA solution for self-hosted",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/commonjs/index.js",
@@ -130,14 +130,14 @@
130
130
  "react-native": "0.79.1",
131
131
  "react-native-builder-bob": "0.41.0",
132
132
  "typescript": "^6.0.2",
133
- "hot-updater": "0.35.5"
133
+ "hot-updater": "0.35.7"
134
134
  },
135
135
  "dependencies": {
136
136
  "use-sync-external-store": "1.5.0",
137
- "@hot-updater/core": "0.35.5",
138
- "@hot-updater/js": "0.35.5",
139
- "@hot-updater/plugin-core": "0.35.5",
140
- "@hot-updater/cli-tools": "0.35.5"
137
+ "@hot-updater/cli-tools": "0.35.7",
138
+ "@hot-updater/core": "0.35.7",
139
+ "@hot-updater/js": "0.35.7",
140
+ "@hot-updater/plugin-core": "0.35.7"
141
141
  },
142
142
  "scripts": {
143
143
  "build": "node ./scripts/sync-sdk-version.mjs run -- bob build && tsc -p plugin/tsconfig.build.json",
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "0.35.5";
1
+ export const HOT_UPDATER_SDK_VERSION = "0.35.7";