@hot-updater/react-native 0.21.13 → 0.21.15
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
-
public enum BundleStorageError: Error {
|
|
3
|
+
public enum BundleStorageError: Error, CustomNSError {
|
|
4
4
|
case bundleNotFound
|
|
5
5
|
case directoryCreationFailed
|
|
6
6
|
case downloadFailed(Error)
|
|
@@ -13,6 +13,92 @@ public enum BundleStorageError: Error {
|
|
|
13
13
|
case copyOperationFailed(Error)
|
|
14
14
|
case fileSystemError(Error)
|
|
15
15
|
case unknown(Error?)
|
|
16
|
+
|
|
17
|
+
// CustomNSError protocol implementation
|
|
18
|
+
public static var errorDomain: String {
|
|
19
|
+
return "com.hotupdater.BundleStorageError"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public var errorCode: Int {
|
|
23
|
+
switch self {
|
|
24
|
+
case .bundleNotFound: return 1001
|
|
25
|
+
case .directoryCreationFailed: return 1002
|
|
26
|
+
case .downloadFailed: return 1003
|
|
27
|
+
case .extractionFailed: return 1004
|
|
28
|
+
case .invalidBundle: return 1005
|
|
29
|
+
case .invalidZipFile: return 1006
|
|
30
|
+
case .insufficientDiskSpace: return 1007
|
|
31
|
+
case .hashMismatch: return 1008
|
|
32
|
+
case .moveOperationFailed: return 1009
|
|
33
|
+
case .copyOperationFailed: return 1010
|
|
34
|
+
case .fileSystemError: return 1011
|
|
35
|
+
case .unknown: return 1099
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public var errorUserInfo: [String: Any] {
|
|
40
|
+
var userInfo: [String: Any] = [:]
|
|
41
|
+
|
|
42
|
+
switch self {
|
|
43
|
+
case .bundleNotFound:
|
|
44
|
+
userInfo[NSLocalizedDescriptionKey] = "Bundle file not found in the downloaded archive"
|
|
45
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Ensure the bundle archive contains index.ios.bundle or main.jsbundle"
|
|
46
|
+
|
|
47
|
+
case .directoryCreationFailed:
|
|
48
|
+
userInfo[NSLocalizedDescriptionKey] = "Failed to create required directory for bundle storage"
|
|
49
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Check app permissions and available disk space"
|
|
50
|
+
|
|
51
|
+
case .downloadFailed(let underlyingError):
|
|
52
|
+
userInfo[NSLocalizedDescriptionKey] = "Failed to download bundle from server"
|
|
53
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
54
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Check network connection and try again"
|
|
55
|
+
|
|
56
|
+
case .extractionFailed(let underlyingError):
|
|
57
|
+
userInfo[NSLocalizedDescriptionKey] = "Failed to extract bundle archive"
|
|
58
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
59
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "The downloaded file may be corrupted. Try downloading again"
|
|
60
|
+
|
|
61
|
+
case .invalidBundle:
|
|
62
|
+
userInfo[NSLocalizedDescriptionKey] = "Downloaded archive does not contain a valid React Native bundle"
|
|
63
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Verify the bundle was built correctly with metro bundler"
|
|
64
|
+
|
|
65
|
+
case .invalidZipFile:
|
|
66
|
+
userInfo[NSLocalizedDescriptionKey] = "Downloaded file is not a valid ZIP archive"
|
|
67
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "The file may be corrupted during download"
|
|
68
|
+
|
|
69
|
+
case .insufficientDiskSpace:
|
|
70
|
+
userInfo[NSLocalizedDescriptionKey] = "Insufficient disk space to download and extract bundle"
|
|
71
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Free up device storage and try again"
|
|
72
|
+
|
|
73
|
+
case .hashMismatch:
|
|
74
|
+
userInfo[NSLocalizedDescriptionKey] = "Downloaded bundle hash does not match expected hash"
|
|
75
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "The file may have been corrupted or tampered with. Try downloading again"
|
|
76
|
+
|
|
77
|
+
case .moveOperationFailed(let underlyingError):
|
|
78
|
+
userInfo[NSLocalizedDescriptionKey] = "Failed to move bundle to final location"
|
|
79
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
80
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Check file system permissions"
|
|
81
|
+
|
|
82
|
+
case .copyOperationFailed(let underlyingError):
|
|
83
|
+
userInfo[NSLocalizedDescriptionKey] = "Failed to copy bundle files"
|
|
84
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
85
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Check available disk space and permissions"
|
|
86
|
+
|
|
87
|
+
case .fileSystemError(let underlyingError):
|
|
88
|
+
userInfo[NSLocalizedDescriptionKey] = "File system operation failed"
|
|
89
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
90
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Check app permissions and disk space"
|
|
91
|
+
|
|
92
|
+
case .unknown(let underlyingError):
|
|
93
|
+
userInfo[NSLocalizedDescriptionKey] = "An unknown error occurred during bundle update"
|
|
94
|
+
if let error = underlyingError {
|
|
95
|
+
userInfo[NSUnderlyingErrorKey] = error
|
|
96
|
+
}
|
|
97
|
+
userInfo[NSLocalizedRecoverySuggestionErrorKey] = "Please try again or contact support with error details"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return userInfo
|
|
101
|
+
}
|
|
16
102
|
}
|
|
17
103
|
|
|
18
104
|
/**
|
|
@@ -477,6 +563,35 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
477
563
|
}
|
|
478
564
|
}
|
|
479
565
|
|
|
566
|
+
/**
|
|
567
|
+
* Logs detailed diagnostic information about a file system path.
|
|
568
|
+
* @param path The path to diagnose
|
|
569
|
+
* @param context Additional context for logging
|
|
570
|
+
*/
|
|
571
|
+
private func logFileSystemDiagnostics(path: String, context: String) {
|
|
572
|
+
let fileManager = FileManager.default
|
|
573
|
+
|
|
574
|
+
// Check if path exists
|
|
575
|
+
let exists = fileManager.fileExists(atPath: path)
|
|
576
|
+
NSLog("[BundleStorage] [\(context)] Path exists: \(exists) - \(path)")
|
|
577
|
+
|
|
578
|
+
if exists {
|
|
579
|
+
do {
|
|
580
|
+
let attributes = try fileManager.attributesOfItem(atPath: path)
|
|
581
|
+
let size = attributes[.size] as? Int64 ?? 0
|
|
582
|
+
let permissions = attributes[.posixPermissions] as? Int ?? 0
|
|
583
|
+
NSLog("[BundleStorage] [\(context)] Size: \(size) bytes, Permissions: \(String(permissions, radix: 8))")
|
|
584
|
+
} catch {
|
|
585
|
+
NSLog("[BundleStorage] [\(context)] Failed to get attributes: \(error.localizedDescription)")
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Check parent directory
|
|
590
|
+
let parentPath = (path as NSString).deletingLastPathComponent
|
|
591
|
+
let parentExists = fileManager.fileExists(atPath: parentPath)
|
|
592
|
+
NSLog("[BundleStorage] [\(context)] Parent directory exists: \(parentExists) - \(parentPath)")
|
|
593
|
+
}
|
|
594
|
+
|
|
480
595
|
/**
|
|
481
596
|
* Processes a downloaded bundle file using the "tmp" rename approach.
|
|
482
597
|
* This method is part of the asynchronous `updateBundle` flow and is expected to run on a background thread.
|
|
@@ -504,6 +619,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
504
619
|
|
|
505
620
|
// 1) Ensure the bundle file exists
|
|
506
621
|
guard self.fileSystem.fileExists(atPath: location.path) else {
|
|
622
|
+
logFileSystemDiagnostics(path: location.path, context: "Download Location Missing")
|
|
507
623
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
508
624
|
completion(.failure(BundleStorageError.fileSystemError(NSError(
|
|
509
625
|
domain: "HotUpdaterError",
|
|
@@ -527,6 +643,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
527
643
|
// 4) Create tmpDir
|
|
528
644
|
try self.fileSystem.createDirectory(atPath: tmpDir)
|
|
529
645
|
NSLog("[BundleStorage] Created tmpDir: \(tmpDir)")
|
|
646
|
+
logFileSystemDiagnostics(path: tmpDir, context: "TmpDir Created")
|
|
530
647
|
|
|
531
648
|
// 5) Verify file hash if provided
|
|
532
649
|
if let expectedHash = fileHash {
|
|
@@ -544,14 +661,18 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
544
661
|
|
|
545
662
|
// 6) Unzip directly into tmpDir with progress tracking (0.8 - 1.0)
|
|
546
663
|
NSLog("[BundleStorage] Extracting \(tempBundleFile) → \(tmpDir)")
|
|
664
|
+
logFileSystemDiagnostics(path: tempBundleFile, context: "Before Extraction")
|
|
547
665
|
do {
|
|
548
666
|
try self.decompressService.unzip(file: tempBundleFile, to: tmpDir, progressHandler: { unzipProgress in
|
|
549
667
|
// Map unzip progress (0.0 - 1.0) to overall progress (0.8 - 1.0)
|
|
550
668
|
progressHandler(0.8 + (unzipProgress * 0.2))
|
|
551
669
|
})
|
|
552
670
|
NSLog("[BundleStorage] Extraction complete at \(tmpDir)")
|
|
671
|
+
logFileSystemDiagnostics(path: tmpDir, context: "After Extraction")
|
|
553
672
|
} catch {
|
|
554
|
-
|
|
673
|
+
let nsError = error as NSError
|
|
674
|
+
NSLog("[BundleStorage] Extraction failed - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
675
|
+
logFileSystemDiagnostics(path: tmpDir, context: "Extraction Failed")
|
|
555
676
|
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
556
677
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
557
678
|
completion(.failure(BundleStorageError.extractionFailed(error)))
|
|
@@ -565,6 +686,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
565
686
|
switch self.findBundleFile(in: tmpDir) {
|
|
566
687
|
case .success(let maybeBundlePath):
|
|
567
688
|
if let bundlePathInTmp = maybeBundlePath {
|
|
689
|
+
NSLog("[BundleStorage] Found valid bundle in tmpDir: \(bundlePathInTmp)")
|
|
690
|
+
logFileSystemDiagnostics(path: bundlePathInTmp, context: "Bundle Found")
|
|
691
|
+
|
|
568
692
|
// 9) Remove any existing realDir
|
|
569
693
|
if self.fileSystem.fileExists(atPath: realDir) {
|
|
570
694
|
try self.fileSystem.removeItem(atPath: realDir)
|
|
@@ -572,8 +696,17 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
572
696
|
}
|
|
573
697
|
|
|
574
698
|
// 10) Rename (move) tmpDir → realDir
|
|
575
|
-
|
|
576
|
-
|
|
699
|
+
do {
|
|
700
|
+
try self.fileSystem.moveItem(atPath: tmpDir, toPath: realDir)
|
|
701
|
+
NSLog("[BundleStorage] Renamed tmpDir to realDir: \(realDir)")
|
|
702
|
+
logFileSystemDiagnostics(path: realDir, context: "After Move")
|
|
703
|
+
} catch {
|
|
704
|
+
let nsError = error as NSError
|
|
705
|
+
NSLog("[BundleStorage] Move operation failed - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
706
|
+
logFileSystemDiagnostics(path: tmpDir, context: "Move Failed - Source")
|
|
707
|
+
logFileSystemDiagnostics(path: realDir, context: "Move Failed - Destination")
|
|
708
|
+
throw BundleStorageError.moveOperationFailed(error)
|
|
709
|
+
}
|
|
577
710
|
|
|
578
711
|
// 11) Construct final bundlePath for preferences
|
|
579
712
|
let finalBundlePath = (realDir as NSString).appendingPathComponent((bundlePathInTmp as NSString).lastPathComponent)
|
|
@@ -582,6 +715,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
582
715
|
let setResult = self.setBundleURL(localPath: finalBundlePath)
|
|
583
716
|
switch setResult {
|
|
584
717
|
case .success:
|
|
718
|
+
NSLog("[BundleStorage] Successfully set bundle URL: \(finalBundlePath)")
|
|
585
719
|
// 13) Clean up the temporary directory
|
|
586
720
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
587
721
|
|
|
@@ -591,6 +725,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
591
725
|
// 15) Complete with success
|
|
592
726
|
completion(.success(true))
|
|
593
727
|
case .failure(let err):
|
|
728
|
+
let nsError = err as NSError
|
|
729
|
+
NSLog("[BundleStorage] Failed to set bundle URL - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
594
730
|
// Preferences save failed → remove realDir and clean up
|
|
595
731
|
try? self.fileSystem.removeItem(atPath: realDir)
|
|
596
732
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
@@ -598,11 +734,15 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
598
734
|
}
|
|
599
735
|
} else {
|
|
600
736
|
// No valid .jsbundle found → delete tmpDir and fail
|
|
737
|
+
NSLog("[BundleStorage] No valid bundle file found in tmpDir")
|
|
738
|
+
logFileSystemDiagnostics(path: tmpDir, context: "Invalid Bundle")
|
|
601
739
|
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
602
740
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
603
741
|
completion(.failure(BundleStorageError.invalidBundle))
|
|
604
742
|
}
|
|
605
743
|
case .failure(let findError):
|
|
744
|
+
let nsError = findError as NSError
|
|
745
|
+
NSLog("[BundleStorage] Error finding bundle file - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
606
746
|
// Error scanning tmpDir → delete tmpDir and fail
|
|
607
747
|
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
608
748
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
@@ -610,7 +750,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
610
750
|
}
|
|
611
751
|
} catch let error {
|
|
612
752
|
// Any failure during unzip or rename → clean tmpDir and fail
|
|
613
|
-
|
|
753
|
+
let nsError = error as NSError
|
|
754
|
+
NSLog("[BundleStorage] Error during tmpDir processing - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
755
|
+
logFileSystemDiagnostics(path: tmpDir, context: "Processing Error")
|
|
614
756
|
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
615
757
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
616
758
|
completion(.failure(BundleStorageError.fileSystemError(error)))
|
|
@@ -174,15 +174,22 @@ import React
|
|
|
174
174
|
NSLog("[HotUpdaterImpl] Update successful for \(bundleId). Resolving promise.")
|
|
175
175
|
resolve(true)
|
|
176
176
|
case .failure(let error):
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
let nsError = error as NSError
|
|
178
|
+
NSLog("[HotUpdaterImpl] Update failed for \(bundleId) - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
179
|
+
|
|
180
|
+
// Create a meaningful error code for React Native
|
|
181
|
+
let errorCode = "BUNDLE_STORAGE_ERROR_\(nsError.code)"
|
|
182
|
+
reject(errorCode, nsError.localizedDescription, nsError)
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
} catch let error {
|
|
183
187
|
// Main error boundary - catch and convert all errors to JS rejection
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
let nsError = error as NSError
|
|
189
|
+
NSLog("[HotUpdaterImpl] Error in updateBundleFromJS - Domain: \(nsError.domain), Code: \(nsError.code), Description: \(nsError.localizedDescription)")
|
|
190
|
+
|
|
191
|
+
let errorCode = "UPDATE_ERROR_\(nsError.code)"
|
|
192
|
+
reject(errorCode, nsError.localizedDescription, nsError)
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
195
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.15",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -119,14 +119,14 @@
|
|
|
119
119
|
"react-native": "0.79.1",
|
|
120
120
|
"react-native-builder-bob": "^0.40.10",
|
|
121
121
|
"typescript": "^5.8.3",
|
|
122
|
-
"hot-updater": "0.21.
|
|
122
|
+
"hot-updater": "0.21.15"
|
|
123
123
|
},
|
|
124
124
|
"dependencies": {
|
|
125
125
|
"use-sync-external-store": "1.5.0",
|
|
126
|
-
"@hot-updater/
|
|
127
|
-
"@hot-updater/
|
|
128
|
-
"@hot-updater/js": "0.21.
|
|
129
|
-
"@hot-updater/plugin-core": "0.21.
|
|
126
|
+
"@hot-updater/core": "0.21.15",
|
|
127
|
+
"@hot-updater/cli-tools": "0.21.15",
|
|
128
|
+
"@hot-updater/js": "0.21.15",
|
|
129
|
+
"@hot-updater/plugin-core": "0.21.15"
|
|
130
130
|
},
|
|
131
131
|
"scripts": {
|
|
132
132
|
"build": "bob build && tsc -p plugin/tsconfig.build.json",
|