@hot-updater/react-native 0.21.13 → 0.21.14

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
- NSLog("[BundleStorage] Extraction failed: \(error.localizedDescription)")
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
- try self.fileSystem.moveItem(atPath: tmpDir, toPath: realDir)
576
- NSLog("[BundleStorage] Renamed tmpDir to realDir: \(realDir)")
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
- NSLog("[BundleStorage] Error during tmpDir processing: \(error)")
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
- NSLog("[HotUpdaterImpl] Update failed for \(bundleId): \(error.localizedDescription). Rejecting promise.")
178
- reject("UPDATE_ERROR", error.localizedDescription, error)
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
- NSLog("[HotUpdaterImpl] Error in updateBundleFromJS: \(error.localizedDescription)")
185
- reject("UPDATE_ERROR", error.localizedDescription, error)
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.13",
3
+ "version": "0.21.14",
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.13"
122
+ "hot-updater": "0.21.14"
123
123
  },
124
124
  "dependencies": {
125
125
  "use-sync-external-store": "1.5.0",
126
- "@hot-updater/cli-tools": "0.21.13",
127
- "@hot-updater/core": "0.21.13",
128
- "@hot-updater/js": "0.21.13",
129
- "@hot-updater/plugin-core": "0.21.13"
126
+ "@hot-updater/cli-tools": "0.21.14",
127
+ "@hot-updater/js": "0.21.14",
128
+ "@hot-updater/plugin-core": "0.21.14",
129
+ "@hot-updater/core": "0.21.14"
130
130
  },
131
131
  "scripts": {
132
132
  "build": "bob build && tsc -p plugin/tsconfig.build.json",