@hot-updater/react-native 0.36.4 → 0.36.6
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/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +37 -37
- package/android/src/main/java/com/hotupdater/RelativePathResolver.kt +2 -1
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +18 -10
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +41 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +152 -23
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/package.json +6 -6
- package/src/sdkVersion.ts +1 -1
|
@@ -547,34 +547,28 @@ class BundleFileStorageService(
|
|
|
547
547
|
return regex.find(currentUrl)?.groupValues?.get(1)
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
private fun resolveBundleFile(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
?.takeIf { File(it).name.endsWith(".android.bundle") }
|
|
560
|
-
}?.singleOrNull()
|
|
561
|
-
|
|
562
|
-
if (manifestBundlePath != null) {
|
|
563
|
-
try {
|
|
564
|
-
val canonicalBundleDir = bundleDir.canonicalFile
|
|
565
|
-
val canonicalBundleFile = File(bundleDir, manifestBundlePath).canonicalFile
|
|
566
|
-
val canonicalBundleDirPath = canonicalBundleDir.path
|
|
567
|
-
val canonicalBundleFilePath = canonicalBundleFile.path
|
|
568
|
-
val isWithinBundleDir =
|
|
569
|
-
canonicalBundleFilePath == canonicalBundleDirPath ||
|
|
570
|
-
canonicalBundleFilePath.startsWith("$canonicalBundleDirPath${File.separator}")
|
|
571
|
-
|
|
572
|
-
if (isWithinBundleDir && canonicalBundleFile.isFile) {
|
|
573
|
-
return canonicalBundleFile
|
|
574
|
-
}
|
|
575
|
-
} catch (e: Exception) {
|
|
576
|
-
Log.w(TAG, "Failed to resolve manifest bundle file from ${bundleDir.absolutePath}: ${e.message}")
|
|
550
|
+
private fun resolveBundleFile(
|
|
551
|
+
bundleDir: File,
|
|
552
|
+
expectedBundleId: String? = null,
|
|
553
|
+
): File? {
|
|
554
|
+
val manifestFile = File(bundleDir, "manifest.json")
|
|
555
|
+
if (manifestFile.exists()) {
|
|
556
|
+
val manifest = parseBundleManifestFromFile(manifestFile) ?: return null
|
|
557
|
+
if (expectedBundleId != null && manifest.bundleId != expectedBundleId) {
|
|
558
|
+
return null
|
|
577
559
|
}
|
|
560
|
+
|
|
561
|
+
val resolvedAssets =
|
|
562
|
+
manifest.assets.keys.associateWith { assetPath ->
|
|
563
|
+
RelativePathResolver
|
|
564
|
+
.resolveInside(bundleDir, assetPath)
|
|
565
|
+
?.takeIf { it.isFile }
|
|
566
|
+
?: return null
|
|
567
|
+
}
|
|
568
|
+
return resolvedAssets
|
|
569
|
+
.filterKeys { assetPath -> File(assetPath).name.endsWith(".android.bundle") }
|
|
570
|
+
.values
|
|
571
|
+
.singleOrNull()
|
|
578
572
|
}
|
|
579
573
|
|
|
580
574
|
return File(bundleDir, "index.android.bundle").absoluteFile.takeIf { it.isFile }
|
|
@@ -582,7 +576,7 @@ class BundleFileStorageService(
|
|
|
582
576
|
|
|
583
577
|
private fun findBundleFile(bundleId: String): File? {
|
|
584
578
|
val bundleDir = File(getBundleStoreDir(), bundleId)
|
|
585
|
-
return resolveBundleFile(bundleDir)
|
|
579
|
+
return resolveBundleFile(bundleDir, bundleId)
|
|
586
580
|
}
|
|
587
581
|
|
|
588
582
|
private fun getBundleUrlForId(bundleId: String): String? = findBundleFile(bundleId)?.absolutePath
|
|
@@ -1090,15 +1084,21 @@ class BundleFileStorageService(
|
|
|
1090
1084
|
return null
|
|
1091
1085
|
}
|
|
1092
1086
|
|
|
1093
|
-
val
|
|
1094
|
-
val
|
|
1095
|
-
|
|
1096
|
-
|
|
1087
|
+
val cachedBundleId = extractBundleIdFromCurrentURL()
|
|
1088
|
+
val resolvedBundle = cachedBundleId?.let(::findBundleFile)
|
|
1089
|
+
val isValid =
|
|
1090
|
+
if (cachedBundleId == null) {
|
|
1091
|
+
File(urlString).isFile
|
|
1092
|
+
} else {
|
|
1093
|
+
resolvedBundle != null
|
|
1094
|
+
}
|
|
1095
|
+
Log.d(TAG, "getCachedBundleURL: cached bundle valid = $isValid at path: $urlString")
|
|
1096
|
+
if (!isValid) {
|
|
1097
1097
|
preferences.setItem("HotUpdaterBundleURL", null)
|
|
1098
|
-
Log.d(TAG, "getCachedBundleURL:
|
|
1098
|
+
Log.d(TAG, "getCachedBundleURL: cached bundle is invalid, cleared preference")
|
|
1099
1099
|
return null
|
|
1100
1100
|
}
|
|
1101
|
-
return urlString
|
|
1101
|
+
return resolvedBundle?.absolutePath ?: urlString
|
|
1102
1102
|
}
|
|
1103
1103
|
|
|
1104
1104
|
override fun getFallbackBundleURL(): String = "assets://index.android.bundle"
|
|
@@ -1180,7 +1180,7 @@ class BundleFileStorageService(
|
|
|
1180
1180
|
val finalBundleDir = File(bundleStoreDir, bundleId)
|
|
1181
1181
|
if (finalBundleDir.exists()) {
|
|
1182
1182
|
Log.d(TAG, "Bundle for bundleId $bundleId already exists. Using cached bundle.")
|
|
1183
|
-
val existingBundleFile = resolveBundleFile(finalBundleDir)
|
|
1183
|
+
val existingBundleFile = resolveBundleFile(finalBundleDir, bundleId)
|
|
1184
1184
|
if (existingBundleFile != null) {
|
|
1185
1185
|
// Update last modified time
|
|
1186
1186
|
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
@@ -1364,7 +1364,7 @@ class BundleFileStorageService(
|
|
|
1364
1364
|
}
|
|
1365
1365
|
|
|
1366
1366
|
// 4) Resolve the extracted Android bundle file.
|
|
1367
|
-
val extractedBundleFile = resolveBundleFile(tmpDir)
|
|
1367
|
+
val extractedBundleFile = resolveBundleFile(tmpDir, bundleId)
|
|
1368
1368
|
if (extractedBundleFile == null) {
|
|
1369
1369
|
Log.d("BundleStorage", "Android bundle file could not be resolved in tmpDir.")
|
|
1370
1370
|
tempDir.deleteRecursively()
|
|
@@ -1403,7 +1403,7 @@ class BundleFileStorageService(
|
|
|
1403
1403
|
}
|
|
1404
1404
|
|
|
1405
1405
|
// 8) Verify the Android bundle file exists inside finalBundleDir.
|
|
1406
|
-
val finalBundleFile = resolveBundleFile(finalBundleDir)
|
|
1406
|
+
val finalBundleFile = resolveBundleFile(finalBundleDir, bundleId)
|
|
1407
1407
|
if (finalBundleFile == null) {
|
|
1408
1408
|
Log.d("BundleStorage", "Android bundle file could not be resolved in realDir.")
|
|
1409
1409
|
tempDir.deleteRecursively()
|
|
@@ -12,7 +12,8 @@ internal object RelativePathResolver {
|
|
|
12
12
|
return null
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Archive directory entries conventionally end with one slash.
|
|
16
|
+
val components = path.removeSuffix("/").split("/")
|
|
16
17
|
if (components.any { it.isEmpty() || it == "." || it == ".." }) {
|
|
17
18
|
return null
|
|
18
19
|
}
|
|
@@ -48,29 +48,29 @@ class BundleFileStorageServiceTest {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
@Test
|
|
51
|
-
fun `resolveBundleFile
|
|
51
|
+
fun `resolveBundleFile rejects a manifest with missing assets even when root index exists`() {
|
|
52
52
|
val rootDir = temporaryFolder.newFolder("no-android-candidate")
|
|
53
53
|
val service = createService(rootDir)
|
|
54
54
|
val bundleDir = createBundleDir(rootDir, "bundle-no-candidate")
|
|
55
|
-
|
|
55
|
+
writeFile(bundleDir, "index.android.bundle")
|
|
56
56
|
|
|
57
57
|
writeManifest(bundleDir, listOf("index.ios.bundle", "assets/image.png"))
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
assertNull(invokeResolveBundleFile(service, bundleDir))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
@Test
|
|
63
|
-
fun `resolveBundleFile
|
|
63
|
+
fun `resolveBundleFile rejects a manifest with multiple android bundle candidates`() {
|
|
64
64
|
val rootDir = temporaryFolder.newFolder("multiple-android-candidates")
|
|
65
65
|
val service = createService(rootDir)
|
|
66
66
|
val bundleDir = createBundleDir(rootDir, "bundle-multiple-candidates")
|
|
67
|
-
|
|
67
|
+
writeFile(bundleDir, "index.android.bundle")
|
|
68
68
|
|
|
69
69
|
writeFile(bundleDir, "foo.android.bundle")
|
|
70
70
|
writeFile(bundleDir, "dist/bar.android.bundle")
|
|
71
71
|
writeManifest(bundleDir, listOf("foo.android.bundle", "dist/bar.android.bundle"))
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
assertNull(invokeResolveBundleFile(service, bundleDir))
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
@Test
|
|
@@ -122,7 +122,14 @@ class BundleFileStorageServiceTest {
|
|
|
122
122
|
val service = createService(rootDir, preferences)
|
|
123
123
|
|
|
124
124
|
val stagingDir = createBundleDir(rootDir, "staging-bundle")
|
|
125
|
-
|
|
125
|
+
writeFile(stagingDir, "dist/staging.android.bundle")
|
|
126
|
+
writeManifest(
|
|
127
|
+
stagingDir,
|
|
128
|
+
listOf(
|
|
129
|
+
"dist/staging.android.bundle",
|
|
130
|
+
"assets/missing.png",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
126
133
|
|
|
127
134
|
val stableDir = createBundleDir(rootDir, "stable-bundle")
|
|
128
135
|
val stableBundleFile = writeFile(stableDir, "dist/stable.android.bundle")
|
|
@@ -141,7 +148,7 @@ class BundleFileStorageServiceTest {
|
|
|
141
148
|
val selection = service.prepareLaunch(null)
|
|
142
149
|
val report = service.notifyAppReady()
|
|
143
150
|
|
|
144
|
-
assertEquals(stableBundleFile.
|
|
151
|
+
assertEquals(stableBundleFile.absolutePath, selection.bundleUrl)
|
|
145
152
|
assertEquals(stableDir.name, selection.launchedBundleId)
|
|
146
153
|
assertFalse(selection.shouldRollbackOnCrash)
|
|
147
154
|
assertFalse(stagingDir.exists())
|
|
@@ -153,7 +160,7 @@ class BundleFileStorageServiceTest {
|
|
|
153
160
|
assertEquals(stableDir.name, metadata?.stagingBundleId)
|
|
154
161
|
assertNull(metadata?.stableBundleId)
|
|
155
162
|
assertFalse(metadata?.verificationPending ?: true)
|
|
156
|
-
assertEquals(stableBundleFile.
|
|
163
|
+
assertEquals(stableBundleFile.absolutePath, preferences.getItem("HotUpdaterBundleURL"))
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
@Test
|
|
@@ -416,9 +423,10 @@ class BundleFileStorageServiceTest {
|
|
|
416
423
|
BundleFileStorageService::class.java.getDeclaredMethod(
|
|
417
424
|
"resolveBundleFile",
|
|
418
425
|
File::class.java,
|
|
426
|
+
String::class.java,
|
|
419
427
|
)
|
|
420
428
|
method.isAccessible = true
|
|
421
|
-
return method.invoke(service, bundleDir) as File?
|
|
429
|
+
return method.invoke(service, bundleDir, bundleDir.name) as File?
|
|
422
430
|
}
|
|
423
431
|
|
|
424
432
|
private fun invokeCanUseManifestDrivenInstall(service: BundleFileStorageService): Boolean {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertNull
|
|
5
|
+
import org.junit.Rule
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
import org.junit.rules.TemporaryFolder
|
|
8
|
+
import java.io.File
|
|
9
|
+
|
|
10
|
+
class RelativePathResolverTest {
|
|
11
|
+
@get:Rule
|
|
12
|
+
val temporaryFolder = TemporaryFolder()
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
fun `resolves a standard archive directory entry ending in a slash`() {
|
|
16
|
+
val root = temporaryFolder.root
|
|
17
|
+
|
|
18
|
+
val resolved = RelativePathResolver.resolveInside(root, "drawable-mdpi/")
|
|
19
|
+
|
|
20
|
+
assertEquals(
|
|
21
|
+
File(root, "drawable-mdpi").canonicalFile,
|
|
22
|
+
resolved?.canonicalFile,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Test
|
|
27
|
+
fun `rejects unsafe archive directory paths ending in a slash`() {
|
|
28
|
+
val root = temporaryFolder.root
|
|
29
|
+
val unsafePaths =
|
|
30
|
+
listOf(
|
|
31
|
+
"/",
|
|
32
|
+
"../",
|
|
33
|
+
"drawable-mdpi/../",
|
|
34
|
+
"drawable-mdpi//",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
unsafePaths.forEach { path ->
|
|
38
|
+
assertNull(path, RelativePathResolver.resolveInside(root, path))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -791,7 +791,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
private func createInitialMetadata() -> BundleMetadata {
|
|
794
|
-
let currentBundleId =
|
|
794
|
+
let currentBundleId = getCachedBundleId()
|
|
795
795
|
return BundleMetadata(
|
|
796
796
|
isolationKey: isolationKey,
|
|
797
797
|
stableBundleId: nil,
|
|
@@ -808,7 +808,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
808
808
|
}
|
|
809
809
|
|
|
810
810
|
private func getActiveBundleId() -> String? {
|
|
811
|
-
if let cachedBundleId =
|
|
811
|
+
if let cachedBundleId = getCachedBundleId() {
|
|
812
812
|
return cachedBundleId
|
|
813
813
|
}
|
|
814
814
|
|
|
@@ -870,12 +870,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
func canUseManifestDrivenInstall() -> Bool {
|
|
873
|
-
guard let
|
|
873
|
+
guard let currentBundleId = getCachedBundleId(),
|
|
874
874
|
case .success(let storeDir) = bundleStoreDir() else {
|
|
875
875
|
return false
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
-
let currentBundleId = currentBundleURL.deletingLastPathComponent().lastPathComponent
|
|
879
878
|
let currentBundleDir = (storeDir as NSString).appendingPathComponent(currentBundleId)
|
|
880
879
|
guard fileSystem.fileExists(atPath: currentBundleDir),
|
|
881
880
|
let snapshot = getActiveBundleMetadataSnapshot(),
|
|
@@ -1141,7 +1140,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1141
1140
|
let fallbackBundleId = metadata.stableBundleId.flatMap { candidate in
|
|
1142
1141
|
if case .success(let storeDir) = bundleStoreDir() {
|
|
1143
1142
|
let stableBundleDir = (storeDir as NSString).appendingPathComponent(candidate)
|
|
1144
|
-
if case .success(let bundlePath) = findBundleFile(
|
|
1143
|
+
if case .success(let bundlePath) = findBundleFile(
|
|
1144
|
+
in: stableBundleDir,
|
|
1145
|
+
expectedBundleId: candidate
|
|
1146
|
+
), bundlePath != nil {
|
|
1145
1147
|
return candidate
|
|
1146
1148
|
}
|
|
1147
1149
|
}
|
|
@@ -1163,7 +1165,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1163
1165
|
if let fallbackBundleId,
|
|
1164
1166
|
case .success(let storeDir) = bundleStoreDir() {
|
|
1165
1167
|
let fallbackBundleDir = (storeDir as NSString).appendingPathComponent(fallbackBundleId)
|
|
1166
|
-
if case .success(let bundlePath) = findBundleFile(
|
|
1168
|
+
if case .success(let bundlePath) = findBundleFile(
|
|
1169
|
+
in: fallbackBundleDir,
|
|
1170
|
+
expectedBundleId: fallbackBundleId
|
|
1171
|
+
), let bundlePath {
|
|
1167
1172
|
let _ = setBundleURL(localPath: bundlePath)
|
|
1168
1173
|
}
|
|
1169
1174
|
} else {
|
|
@@ -1278,9 +1283,45 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1278
1283
|
* @param directoryPath Directory to search in
|
|
1279
1284
|
* @return Result with path to bundle file or error
|
|
1280
1285
|
*/
|
|
1281
|
-
func findBundleFile(
|
|
1286
|
+
func findBundleFile(
|
|
1287
|
+
in directoryPath: String,
|
|
1288
|
+
expectedBundleId: String? = nil
|
|
1289
|
+
) -> Result<String?, Error> {
|
|
1282
1290
|
NSLog("[BundleStorage] Searching for bundle file in directory: \(directoryPath)")
|
|
1283
1291
|
|
|
1292
|
+
let manifestPath = (directoryPath as NSString).appendingPathComponent("manifest.json")
|
|
1293
|
+
if fileSystem.fileExists(atPath: manifestPath) {
|
|
1294
|
+
guard let manifest = readManifest(in: directoryPath),
|
|
1295
|
+
let parsedManifest = parseBundleManifest(from: manifest),
|
|
1296
|
+
expectedBundleId == nil || parsedManifest.bundleId == expectedBundleId else {
|
|
1297
|
+
return .success(nil)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
do {
|
|
1301
|
+
var bundlePaths: [String] = []
|
|
1302
|
+
for assetPath in parsedManifest.assets.keys {
|
|
1303
|
+
let assetURL = try ArchiveExtractionUtilities.extractionURL(
|
|
1304
|
+
for: assetPath,
|
|
1305
|
+
destinationRoot: directoryPath
|
|
1306
|
+
)
|
|
1307
|
+
guard fileSystem.fileExists(atPath: assetURL.path),
|
|
1308
|
+
let attributes = try? fileSystem.attributesOfItem(atPath: assetURL.path),
|
|
1309
|
+
attributes[.type] as? FileAttributeType == .typeRegular else {
|
|
1310
|
+
return .success(nil)
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
let fileName = assetURL.lastPathComponent
|
|
1314
|
+
if fileName.hasSuffix(".bundle") || fileName == "main.jsbundle" {
|
|
1315
|
+
bundlePaths.append(assetURL.path)
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
return .success(bundlePaths.count == 1 ? bundlePaths[0] : nil)
|
|
1320
|
+
} catch {
|
|
1321
|
+
return .failure(error)
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1284
1325
|
let iosBundlePath = (directoryPath as NSString).appendingPathComponent("index.ios.bundle")
|
|
1285
1326
|
if self.fileSystem.fileExists(atPath: iosBundlePath) {
|
|
1286
1327
|
NSLog("[BundleStorage] Found iOS bundle atPath: \(iosBundlePath)")
|
|
@@ -1415,18 +1456,91 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1415
1456
|
return .failure(error)
|
|
1416
1457
|
}
|
|
1417
1458
|
}
|
|
1459
|
+
|
|
1460
|
+
private func bundleId(for bundleURL: URL, storeDir: String) -> String? {
|
|
1461
|
+
let storeURL = URL(fileURLWithPath: storeDir, isDirectory: true).standardizedFileURL
|
|
1462
|
+
let bundleURL = bundleURL.standardizedFileURL
|
|
1463
|
+
let storePrefix = storeURL.path + "/"
|
|
1464
|
+
|
|
1465
|
+
guard bundleURL.path.hasPrefix(storePrefix) else {
|
|
1466
|
+
return nil
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
return bundleURL.path
|
|
1470
|
+
.dropFirst(storePrefix.count)
|
|
1471
|
+
.split(separator: "/")
|
|
1472
|
+
.first
|
|
1473
|
+
.map(String.init)
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
private func getCachedBundleId() -> String? {
|
|
1477
|
+
guard let bundleURL = getCachedBundleURL(),
|
|
1478
|
+
case .success(let storeDir) = bundleStoreDir() else {
|
|
1479
|
+
return nil
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
return bundleId(for: bundleURL, storeDir: storeDir)
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
func resolveBundlePathAfterMove(
|
|
1486
|
+
_ bundlePath: String,
|
|
1487
|
+
from sourceDirectory: String,
|
|
1488
|
+
to destinationDirectory: String
|
|
1489
|
+
) throws -> String {
|
|
1490
|
+
let sourceURL = URL(
|
|
1491
|
+
fileURLWithPath: sourceDirectory,
|
|
1492
|
+
isDirectory: true
|
|
1493
|
+
).standardizedFileURL
|
|
1494
|
+
let bundleURL = URL(fileURLWithPath: bundlePath).standardizedFileURL
|
|
1495
|
+
let sourcePrefix = sourceURL.path + "/"
|
|
1496
|
+
|
|
1497
|
+
guard bundleURL.path.hasPrefix(sourcePrefix) else {
|
|
1498
|
+
throw BundleStorageError.invalidBundle
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
let relativePath = String(bundleURL.path.dropFirst(sourcePrefix.count))
|
|
1502
|
+
guard ArchiveExtractionUtilities.normalizedRelativePath(from: relativePath) == relativePath else {
|
|
1503
|
+
throw BundleStorageError.invalidBundle
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
return try ArchiveExtractionUtilities.extractionURL(
|
|
1507
|
+
for: relativePath,
|
|
1508
|
+
destinationRoot: destinationDirectory
|
|
1509
|
+
).path
|
|
1510
|
+
}
|
|
1418
1511
|
|
|
1419
1512
|
/**
|
|
1420
1513
|
* Gets the URL to the cached bundle file if it exists.
|
|
1421
1514
|
*/
|
|
1422
1515
|
func getCachedBundleURL() -> URL? {
|
|
1423
1516
|
do {
|
|
1424
|
-
guard let savedURLString = try self.preferences.getItem(forKey: "HotUpdaterBundleURL")
|
|
1425
|
-
let bundleURL = URL(string: savedURLString),
|
|
1426
|
-
self.fileSystem.fileExists(atPath: bundleURL.path) else {
|
|
1517
|
+
guard let savedURLString = try self.preferences.getItem(forKey: "HotUpdaterBundleURL") else {
|
|
1427
1518
|
return nil
|
|
1428
1519
|
}
|
|
1429
|
-
|
|
1520
|
+
|
|
1521
|
+
let savedURL = URL(string: savedURLString)
|
|
1522
|
+
let bundleURL = (
|
|
1523
|
+
(savedURL?.isFileURL == true ? savedURL : nil)
|
|
1524
|
+
?? URL(fileURLWithPath: savedURLString)
|
|
1525
|
+
).standardizedFileURL
|
|
1526
|
+
guard self.fileSystem.fileExists(atPath: bundleURL.path),
|
|
1527
|
+
case .success(let storeDir) = bundleStoreDir() else {
|
|
1528
|
+
return nil
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
let storeURL = URL(fileURLWithPath: storeDir, isDirectory: true).standardizedFileURL
|
|
1532
|
+
guard let bundleId = bundleId(for: bundleURL, storeDir: storeDir) else {
|
|
1533
|
+
return nil
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
let bundleDirectory = storeURL.appendingPathComponent(bundleId).path
|
|
1537
|
+
guard case .success(let resolvedBundlePath) = findBundleFile(
|
|
1538
|
+
in: bundleDirectory,
|
|
1539
|
+
expectedBundleId: bundleId
|
|
1540
|
+
), let resolvedBundlePath else {
|
|
1541
|
+
return nil
|
|
1542
|
+
}
|
|
1543
|
+
return URL(fileURLWithPath: resolvedBundlePath)
|
|
1430
1544
|
} catch {
|
|
1431
1545
|
NSLog("[BundleStorage] Error getting cached bundle URL: \(error.localizedDescription)")
|
|
1432
1546
|
return nil
|
|
@@ -1446,7 +1560,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1446
1560
|
guard let metadata = loadMetadataOrNull() else {
|
|
1447
1561
|
return LaunchSelection(
|
|
1448
1562
|
bundleURL: getCachedBundleURL() ?? getFallbackBundleURL(bundle: bundle),
|
|
1449
|
-
launchedBundleId:
|
|
1563
|
+
launchedBundleId: getCachedBundleId(),
|
|
1450
1564
|
shouldRollbackOnCrash: false
|
|
1451
1565
|
)
|
|
1452
1566
|
}
|
|
@@ -1454,7 +1568,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1454
1568
|
if let stagingId = metadata.stagingBundleId,
|
|
1455
1569
|
case .success(let storeDir) = bundleStoreDir() {
|
|
1456
1570
|
let stagingBundleDir = (storeDir as NSString).appendingPathComponent(stagingId)
|
|
1457
|
-
if case .success(let bundlePath) = findBundleFile(
|
|
1571
|
+
if case .success(let bundlePath) = findBundleFile(
|
|
1572
|
+
in: stagingBundleDir,
|
|
1573
|
+
expectedBundleId: stagingId
|
|
1574
|
+
), let bundlePath {
|
|
1458
1575
|
return LaunchSelection(
|
|
1459
1576
|
bundleURL: URL(fileURLWithPath: bundlePath),
|
|
1460
1577
|
launchedBundleId: stagingId,
|
|
@@ -1470,7 +1587,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1470
1587
|
if let stableId = metadata.stableBundleId,
|
|
1471
1588
|
case .success(let storeDir) = bundleStoreDir() {
|
|
1472
1589
|
let stableBundleDir = (storeDir as NSString).appendingPathComponent(stableId)
|
|
1473
|
-
if case .success(let bundlePath) = findBundleFile(
|
|
1590
|
+
if case .success(let bundlePath) = findBundleFile(
|
|
1591
|
+
in: stableBundleDir,
|
|
1592
|
+
expectedBundleId: stableId
|
|
1593
|
+
), let bundlePath {
|
|
1474
1594
|
return LaunchSelection(
|
|
1475
1595
|
bundleURL: URL(fileURLWithPath: bundlePath),
|
|
1476
1596
|
launchedBundleId: stableId,
|
|
@@ -1512,7 +1632,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1512
1632
|
}
|
|
1513
1633
|
|
|
1514
1634
|
// Get the current bundle ID from the cached bundle URL (exclude fallback bundles)
|
|
1515
|
-
let currentBundleId = self.
|
|
1635
|
+
let currentBundleId = self.getCachedBundleId()
|
|
1516
1636
|
|
|
1517
1637
|
guard let validFileUrl = fileUrl else {
|
|
1518
1638
|
NSLog("[BundleStorage] fileUrl is nil, resetting bundle URL.")
|
|
@@ -1552,7 +1672,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1552
1672
|
let finalBundleDir = (storeDir as NSString).appendingPathComponent(bundleId)
|
|
1553
1673
|
|
|
1554
1674
|
if self.fileSystem.fileExists(atPath: finalBundleDir) {
|
|
1555
|
-
let findResult = self.findBundleFile(
|
|
1675
|
+
let findResult = self.findBundleFile(
|
|
1676
|
+
in: finalBundleDir,
|
|
1677
|
+
expectedBundleId: bundleId
|
|
1678
|
+
)
|
|
1556
1679
|
switch findResult {
|
|
1557
1680
|
case .success(let existingBundlePath):
|
|
1558
1681
|
if let bundlePath = existingBundlePath {
|
|
@@ -1670,7 +1793,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1670
1793
|
return
|
|
1671
1794
|
}
|
|
1672
1795
|
|
|
1673
|
-
let currentBundleId = self.
|
|
1796
|
+
let currentBundleId = self.getCachedBundleId()
|
|
1674
1797
|
let currentBundleDir = currentBundleId.map {
|
|
1675
1798
|
(storeDir as NSString).appendingPathComponent($0)
|
|
1676
1799
|
}
|
|
@@ -1969,7 +2092,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1969
2092
|
let manifestDestination = (tmpDir as NSString).appendingPathComponent("manifest.json")
|
|
1970
2093
|
try writeManifestFile(targetManifest, to: manifestDestination)
|
|
1971
2094
|
|
|
1972
|
-
switch self.findBundleFile(in: tmpDir) {
|
|
2095
|
+
switch self.findBundleFile(in: tmpDir, expectedBundleId: bundleId) {
|
|
1973
2096
|
case .success(let maybeBundlePath):
|
|
1974
2097
|
guard let bundlePathInTmp = maybeBundlePath else {
|
|
1975
2098
|
throw BundleStorageError.invalidBundle
|
|
@@ -1985,8 +2108,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1985
2108
|
throw BundleStorageError.moveOperationFailed(error)
|
|
1986
2109
|
}
|
|
1987
2110
|
|
|
1988
|
-
let finalBundlePath =
|
|
1989
|
-
|
|
2111
|
+
let finalBundlePath = try resolveBundlePathAfterMove(
|
|
2112
|
+
bundlePathInTmp,
|
|
2113
|
+
from: tmpDir,
|
|
2114
|
+
to: realDir
|
|
1990
2115
|
)
|
|
1991
2116
|
switch self.setBundleURL(localPath: finalBundlePath) {
|
|
1992
2117
|
case .success:
|
|
@@ -2199,7 +2324,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2199
2324
|
progressHandler: @escaping (UpdateProgressPayload) -> Void,
|
|
2200
2325
|
completion: @escaping (Result<Bool, Error>) -> Void
|
|
2201
2326
|
) {
|
|
2202
|
-
let currentBundleId = self.
|
|
2327
|
+
let currentBundleId = self.getCachedBundleId()
|
|
2203
2328
|
NSLog("[BundleStorage] Processing downloaded file atPath: \(location.path)")
|
|
2204
2329
|
|
|
2205
2330
|
// 1) Ensure the bundle file exists
|
|
@@ -2289,7 +2414,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2289
2414
|
progressHandler: progressHandler,
|
|
2290
2415
|
progress: UpdateProgress.bundleValidation
|
|
2291
2416
|
)
|
|
2292
|
-
switch self.findBundleFile(in: tmpDir) {
|
|
2417
|
+
switch self.findBundleFile(in: tmpDir, expectedBundleId: bundleId) {
|
|
2293
2418
|
case .success(let maybeBundlePath):
|
|
2294
2419
|
if let bundlePathInTmp = maybeBundlePath {
|
|
2295
2420
|
NSLog("[BundleStorage] Found valid bundle in tmpDir: \(bundlePathInTmp)")
|
|
@@ -2315,7 +2440,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2315
2440
|
}
|
|
2316
2441
|
|
|
2317
2442
|
// 11) Construct final bundlePath for preferences
|
|
2318
|
-
let finalBundlePath =
|
|
2443
|
+
let finalBundlePath = try resolveBundlePathAfterMove(
|
|
2444
|
+
bundlePathInTmp,
|
|
2445
|
+
from: tmpDir,
|
|
2446
|
+
to: realDir
|
|
2447
|
+
)
|
|
2319
2448
|
|
|
2320
2449
|
// 12) Set the bundle URL in preferences (for backwards compatibility)
|
|
2321
2450
|
let setResult = self.setBundleURL(localPath: finalBundlePath)
|
|
@@ -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.36.
|
|
7
|
+
const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "0.36.6";
|
|
8
8
|
//# sourceMappingURL=sdkVersion.js.map
|
package/lib/module/sdkVersion.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HOT_UPDATER_SDK_VERSION = "0.36.
|
|
1
|
+
export declare const HOT_UPDATER_SDK_VERSION = "0.36.6";
|
|
2
2
|
//# sourceMappingURL=sdkVersion.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HOT_UPDATER_SDK_VERSION = "0.36.
|
|
1
|
+
export declare const HOT_UPDATER_SDK_VERSION = "0.36.6";
|
|
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.36.
|
|
3
|
+
"version": "0.36.6",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/commonjs/index.js",
|
|
@@ -131,14 +131,14 @@
|
|
|
131
131
|
"react-native-builder-bob": "0.41.0",
|
|
132
132
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
133
133
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
134
|
-
"hot-updater": "0.36.
|
|
134
|
+
"hot-updater": "0.36.6"
|
|
135
135
|
},
|
|
136
136
|
"dependencies": {
|
|
137
137
|
"use-sync-external-store": "1.5.0",
|
|
138
|
-
"@hot-updater/cli-tools": "0.36.
|
|
139
|
-
"@hot-updater/core": "0.36.
|
|
140
|
-
"@hot-updater/js": "0.36.
|
|
141
|
-
"@hot-updater/plugin-core": "0.36.
|
|
138
|
+
"@hot-updater/cli-tools": "0.36.6",
|
|
139
|
+
"@hot-updater/core": "0.36.6",
|
|
140
|
+
"@hot-updater/js": "0.36.6",
|
|
141
|
+
"@hot-updater/plugin-core": "0.36.6"
|
|
142
142
|
},
|
|
143
143
|
"scripts": {
|
|
144
144
|
"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.36.
|
|
1
|
+
export const HOT_UPDATER_SDK_VERSION = "0.36.6";
|