@hot-updater/react-native 0.23.0 → 0.24.0

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.
Files changed (91) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +393 -49
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +204 -0
  3. package/android/src/main/java/com/hotupdater/HotUpdater.kt +48 -36
  4. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +134 -0
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +168 -95
  6. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +15 -3
  7. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +17 -12
  8. package/android/src/newarch/HotUpdaterModule.kt +88 -23
  9. package/android/src/oldarch/HotUpdaterModule.kt +89 -22
  10. package/android/src/oldarch/HotUpdaterSpec.kt +6 -0
  11. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +401 -77
  12. package/ios/HotUpdater/Internal/BundleMetadata.swift +177 -0
  13. package/ios/HotUpdater/Internal/HotUpdater.mm +213 -47
  14. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +96 -25
  15. package/ios/HotUpdater/Internal/SignatureVerifier.swift +35 -29
  16. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +2 -2
  17. package/ios/HotUpdater/Public/HotUpdater.h +8 -2
  18. package/lib/commonjs/checkForUpdate.js +31 -28
  19. package/lib/commonjs/checkForUpdate.js.map +1 -1
  20. package/lib/commonjs/error.js +45 -1
  21. package/lib/commonjs/error.js.map +1 -1
  22. package/lib/commonjs/fetchUpdateInfo.js +7 -45
  23. package/lib/commonjs/fetchUpdateInfo.js.map +1 -1
  24. package/lib/commonjs/index.js +237 -208
  25. package/lib/commonjs/index.js.map +1 -1
  26. package/lib/commonjs/native.js +103 -3
  27. package/lib/commonjs/native.js.map +1 -1
  28. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  29. package/lib/commonjs/wrap.js +39 -1
  30. package/lib/commonjs/wrap.js.map +1 -1
  31. package/lib/module/checkForUpdate.js +32 -26
  32. package/lib/module/checkForUpdate.js.map +1 -1
  33. package/lib/module/error.js +45 -0
  34. package/lib/module/error.js.map +1 -1
  35. package/lib/module/fetchUpdateInfo.js +7 -45
  36. package/lib/module/fetchUpdateInfo.js.map +1 -1
  37. package/lib/module/index.js +238 -203
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/native.js +87 -2
  40. package/lib/module/native.js.map +1 -1
  41. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  42. package/lib/module/wrap.js +40 -2
  43. package/lib/module/wrap.js.map +1 -1
  44. package/lib/typescript/commonjs/checkForUpdate.d.ts +11 -13
  45. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/error.d.ts +120 -0
  47. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +3 -5
  49. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/index.d.ts +35 -41
  51. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/native.d.ts +58 -2
  53. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +62 -0
  55. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/wrap.d.ts +76 -5
  57. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  58. package/lib/typescript/module/checkForUpdate.d.ts +11 -13
  59. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  60. package/lib/typescript/module/error.d.ts +120 -0
  61. package/lib/typescript/module/error.d.ts.map +1 -1
  62. package/lib/typescript/module/fetchUpdateInfo.d.ts +3 -5
  63. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -1
  64. package/lib/typescript/module/index.d.ts +35 -41
  65. package/lib/typescript/module/index.d.ts.map +1 -1
  66. package/lib/typescript/module/native.d.ts +58 -2
  67. package/lib/typescript/module/native.d.ts.map +1 -1
  68. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +62 -0
  69. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  70. package/lib/typescript/module/wrap.d.ts +76 -5
  71. package/lib/typescript/module/wrap.d.ts.map +1 -1
  72. package/package.json +8 -7
  73. package/plugin/build/withHotUpdater.js +55 -4
  74. package/src/checkForUpdate.ts +51 -40
  75. package/src/error.ts +153 -0
  76. package/src/fetchUpdateInfo.ts +10 -58
  77. package/src/index.ts +283 -206
  78. package/src/native.ts +88 -2
  79. package/src/specs/NativeHotUpdater.ts +63 -0
  80. package/src/wrap.tsx +131 -9
  81. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +0 -52
  82. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +0 -24
  83. package/lib/commonjs/runUpdateProcess.js +0 -69
  84. package/lib/commonjs/runUpdateProcess.js.map +0 -1
  85. package/lib/module/runUpdateProcess.js +0 -64
  86. package/lib/module/runUpdateProcess.js.map +0 -1
  87. package/lib/typescript/commonjs/runUpdateProcess.d.ts +0 -49
  88. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +0 -1
  89. package/lib/typescript/module/runUpdateProcess.d.ts +0 -49
  90. package/lib/typescript/module/runUpdateProcess.d.ts.map +0 -1
  91. package/src/runUpdateProcess.ts +0 -80
@@ -0,0 +1,177 @@
1
+ import Foundation
2
+
3
+ // MARK: - BundleMetadata
4
+
5
+ /// Bundle metadata for managing stable/staging bundles and verification state
6
+ public struct BundleMetadata: Codable {
7
+ static let schemaVersion = "metadata-v1"
8
+ static let metadataFilename = "metadata.json"
9
+
10
+ let schema: String
11
+ var stableBundleId: String?
12
+ var stagingBundleId: String?
13
+ var verificationPending: Bool
14
+ var verificationAttemptedAt: Double?
15
+ var stagingExecutionCount: Int?
16
+ var updatedAt: Double
17
+
18
+ enum CodingKeys: String, CodingKey {
19
+ case schema
20
+ case stableBundleId = "stable_bundle_id"
21
+ case stagingBundleId = "staging_bundle_id"
22
+ case verificationPending = "verification_pending"
23
+ case verificationAttemptedAt = "verification_attempted_at"
24
+ case stagingExecutionCount = "staging_execution_count"
25
+ case updatedAt = "updated_at"
26
+ }
27
+
28
+ init(
29
+ schema: String = BundleMetadata.schemaVersion,
30
+ stableBundleId: String? = nil,
31
+ stagingBundleId: String? = nil,
32
+ verificationPending: Bool = false,
33
+ verificationAttemptedAt: Double? = nil,
34
+ stagingExecutionCount: Int? = nil,
35
+ updatedAt: Double = Date().timeIntervalSince1970 * 1000
36
+ ) {
37
+ self.schema = schema
38
+ self.stableBundleId = stableBundleId
39
+ self.stagingBundleId = stagingBundleId
40
+ self.verificationPending = verificationPending
41
+ self.verificationAttemptedAt = verificationAttemptedAt
42
+ self.stagingExecutionCount = stagingExecutionCount
43
+ self.updatedAt = updatedAt
44
+ }
45
+
46
+ static func load(from file: URL) -> BundleMetadata? {
47
+ guard FileManager.default.fileExists(atPath: file.path) else {
48
+ print("[BundleMetadata] Metadata file does not exist: \(file.path)")
49
+ return nil
50
+ }
51
+
52
+ do {
53
+ let data = try Data(contentsOf: file)
54
+ let decoder = JSONDecoder()
55
+ let metadata = try decoder.decode(BundleMetadata.self, from: data)
56
+ return metadata
57
+ } catch {
58
+ print("[BundleMetadata] Failed to load metadata from file: \(error)")
59
+ return nil
60
+ }
61
+ }
62
+
63
+ func save(to file: URL) -> Bool {
64
+ do {
65
+ let encoder = JSONEncoder()
66
+ encoder.outputFormatting = .prettyPrinted
67
+ let data = try encoder.encode(self)
68
+
69
+ // Create directory if needed
70
+ let directory = file.deletingLastPathComponent()
71
+ if !FileManager.default.fileExists(atPath: directory.path) {
72
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
73
+ }
74
+
75
+ try data.write(to: file)
76
+ print("[BundleMetadata] Saved metadata to file: \(file.path)")
77
+ return true
78
+ } catch {
79
+ print("[BundleMetadata] Failed to save metadata to file: \(error)")
80
+ return false
81
+ }
82
+ }
83
+ }
84
+
85
+ // MARK: - CrashedBundleEntry
86
+
87
+ /// Entry for a crashed bundle in history
88
+ public struct CrashedBundleEntry: Codable {
89
+ let bundleId: String
90
+ var crashedAt: Double
91
+ var crashCount: Int
92
+
93
+ init(bundleId: String, crashedAt: Double = Date().timeIntervalSince1970 * 1000, crashCount: Int = 1) {
94
+ self.bundleId = bundleId
95
+ self.crashedAt = crashedAt
96
+ self.crashCount = crashCount
97
+ }
98
+ }
99
+
100
+ // MARK: - CrashedHistory
101
+
102
+ /// History of crashed bundles
103
+ public struct CrashedHistory: Codable {
104
+ static let defaultMaxHistorySize = 10
105
+ static let crashedHistoryFilename = "crashed-history.json"
106
+
107
+ var bundles: [CrashedBundleEntry]
108
+ var maxHistorySize: Int
109
+
110
+ init(bundles: [CrashedBundleEntry] = [], maxHistorySize: Int = CrashedHistory.defaultMaxHistorySize) {
111
+ self.bundles = bundles
112
+ self.maxHistorySize = maxHistorySize
113
+ }
114
+
115
+ static func load(from file: URL) -> CrashedHistory {
116
+ guard FileManager.default.fileExists(atPath: file.path) else {
117
+ print("[CrashedHistory] Crashed history file does not exist, returning empty history")
118
+ return CrashedHistory()
119
+ }
120
+
121
+ do {
122
+ let data = try Data(contentsOf: file)
123
+ let decoder = JSONDecoder()
124
+ let history = try decoder.decode(CrashedHistory.self, from: data)
125
+ return history
126
+ } catch {
127
+ print("[CrashedHistory] Failed to load crashed history from file: \(error)")
128
+ return CrashedHistory()
129
+ }
130
+ }
131
+
132
+ func save(to file: URL) -> Bool {
133
+ do {
134
+ let encoder = JSONEncoder()
135
+ encoder.outputFormatting = .prettyPrinted
136
+ let data = try encoder.encode(self)
137
+
138
+ // Create directory if needed
139
+ let directory = file.deletingLastPathComponent()
140
+ if !FileManager.default.fileExists(atPath: directory.path) {
141
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
142
+ }
143
+
144
+ try data.write(to: file)
145
+ print("[CrashedHistory] Saved crashed history to file: \(file.path)")
146
+ return true
147
+ } catch {
148
+ print("[CrashedHistory] Failed to save crashed history to file: \(error)")
149
+ return false
150
+ }
151
+ }
152
+
153
+ func contains(_ bundleId: String) -> Bool {
154
+ return bundles.contains { $0.bundleId == bundleId }
155
+ }
156
+
157
+ mutating func addEntry(_ bundleId: String) {
158
+ if let index = bundles.firstIndex(where: { $0.bundleId == bundleId }) {
159
+ // Update existing entry
160
+ bundles[index].crashedAt = Date().timeIntervalSince1970 * 1000
161
+ bundles[index].crashCount += 1
162
+ } else {
163
+ // Add new entry
164
+ bundles.append(CrashedBundleEntry(bundleId: bundleId))
165
+ }
166
+
167
+ // Trim to max size (keep most recent)
168
+ if bundles.count > maxHistorySize {
169
+ bundles.sort { $0.crashedAt < $1.crashedAt }
170
+ bundles = Array(bundles.suffix(maxHistorySize))
171
+ }
172
+ }
173
+
174
+ mutating func clear() {
175
+ bundles.removeAll()
176
+ }
177
+ }
@@ -14,9 +14,6 @@
14
14
  NSNotificationName const HotUpdaterDownloadProgressUpdateNotification = @"HotUpdaterDownloadProgressUpdate";
15
15
  NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterDownloadDidFinish";
16
16
 
17
- // Create static HotUpdaterImpl instance
18
- static HotUpdaterImpl *_hotUpdaterImpl = [HotUpdaterFactory.shared create];
19
-
20
17
  @implementation HotUpdater {
21
18
  bool hasListeners;
22
19
  // Keep track of tasks ONLY for removing observers when this ObjC instance is invalidated
@@ -33,15 +30,14 @@ static HotUpdaterImpl *_hotUpdaterImpl = [HotUpdaterFactory.shared create];
33
30
  observedTasks = [NSMutableSet set];
34
31
 
35
32
  // Start observing notifications needed for cleanup/events
36
- // Using self as observer
37
33
  [[NSNotificationCenter defaultCenter] addObserver:self
38
34
  selector:@selector(handleDownloadProgress:)
39
35
  name:HotUpdaterDownloadProgressUpdateNotification
40
- object:nil]; // Observe all tasks from Impl
36
+ object:nil];
41
37
  [[NSNotificationCenter defaultCenter] addObserver:self
42
38
  selector:@selector(handleDownloadCompletion:)
43
39
  name:HotUpdaterDownloadDidFinishNotification
44
- object:nil]; // Observe all tasks from Impl
40
+ object:nil];
45
41
 
46
42
  _lastUpdateTime = 0;
47
43
  }
@@ -64,6 +60,18 @@ static HotUpdaterImpl *_hotUpdaterImpl = [HotUpdaterFactory.shared create];
64
60
 
65
61
  RCT_EXPORT_MODULE();
66
62
 
63
+ #pragma mark - Singleton Instance
64
+
65
+ // Static singleton HotUpdaterImpl getter
66
+ + (HotUpdaterImpl *)sharedImpl {
67
+ static HotUpdaterImpl *_sharedImpl = nil;
68
+ static dispatch_once_t onceToken;
69
+ dispatch_once(&onceToken, ^{
70
+ _sharedImpl = [[HotUpdaterImpl alloc] init];
71
+ });
72
+ return _sharedImpl;
73
+ }
74
+
67
75
  #pragma mark - React Native Constants (Keep getMinBundleId, delegate others)
68
76
 
69
77
  // Keep local implementation if complex or uses macros
@@ -74,8 +82,37 @@ RCT_EXPORT_MODULE();
74
82
  #if DEBUG
75
83
  uuid = @"00000000-0000-0000-0000-000000000000";
76
84
  #else
85
+ // Step 1: Try to read HOT_UPDATER_BUILD_TIMESTAMP from Info.plist
86
+ NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
87
+ NSString *customValue = infoDictionary[@"HOT_UPDATER_BUILD_TIMESTAMP"];
88
+
89
+ // Step 2: If custom value exists and is not empty
90
+ if (customValue && customValue.length > 0 && ![customValue isEqualToString:@"$(HOT_UPDATER_BUILD_TIMESTAMP)"]) {
91
+ // Check if it's a timestamp (pure digits) or UUID
92
+ NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
93
+ BOOL isTimestamp = ([customValue rangeOfCharacterFromSet:nonDigits].location == NSNotFound);
94
+
95
+ if (isTimestamp) {
96
+ // Convert timestamp (milliseconds) to UUID v7
97
+ uint64_t timestampMs = [customValue longLongValue];
98
+ uuid = [self generateUUIDv7FromTimestamp:timestampMs];
99
+ RCTLogInfo(@"[HotUpdater.mm] Using timestamp %@ as MIN_BUNDLE_ID: %@", customValue, uuid);
100
+ } else {
101
+ // Use as UUID directly
102
+ uuid = customValue;
103
+ RCTLogInfo(@"[HotUpdater.mm] Using custom MIN_BUNDLE_ID from Info.plist: %@", uuid);
104
+ }
105
+ return;
106
+ }
107
+
108
+ // Step 3: Fallback to default logic (26-hour subtraction)
109
+ RCTLogInfo(@"[HotUpdater.mm] No custom MIN_BUNDLE_ID found, using default calculation");
110
+
77
111
  NSString *compileDateStr = [NSString stringWithFormat:@"%s %s", __DATE__, __TIME__];
78
112
  NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
113
+
114
+ // Parse __DATE__ __TIME__ as UTC to ensure consistent timezone handling across all build environments
115
+ [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
79
116
  [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
80
117
  [formatter setDateFormat:@"MMM d yyyy HH:mm:ss"]; // Correct format for __DATE__ __TIME__
81
118
  NSDate *buildDate = [formatter dateFromString:compileDateStr];
@@ -84,43 +121,71 @@ RCT_EXPORT_MODULE();
84
121
  uuid = @"00000000-0000-0000-0000-000000000000";
85
122
  return;
86
123
  }
87
- uint64_t buildTimestampMs = (uint64_t)([buildDate timeIntervalSince1970] * 1000.0);
88
- unsigned char bytes[16];
89
- bytes[0] = (buildTimestampMs >> 40) & 0xFF; // ... rest of UUID logic ...
90
- bytes[1] = (buildTimestampMs >> 32) & 0xFF;
91
- bytes[2] = (buildTimestampMs >> 24) & 0xFF;
92
- bytes[3] = (buildTimestampMs >> 16) & 0xFF;
93
- bytes[4] = (buildTimestampMs >> 8) & 0xFF;
94
- bytes[5] = buildTimestampMs & 0xFF;
95
- bytes[6] = 0x70; bytes[7] = 0x00; bytes[8] = 0x80; bytes[9] = 0x00;
96
- bytes[10] = 0x00; bytes[11] = 0x00; bytes[12] = 0x00; bytes[13] = 0x00; bytes[14] = 0x00; bytes[15] = 0x00;
97
- uuid = [NSString stringWithFormat:
98
- @"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
99
- bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
100
- bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]];
124
+
125
+ // Subtract 26 hours (93600 seconds) to ensure MIN_BUNDLE_ID is always in the past
126
+ // This guarantees that uuidv7-based bundleIds (generated at runtime) will always be newer than MIN_BUNDLE_ID
127
+ // Why 26 hours? Global timezone range spans from UTC-12 to UTC+14 (total 26 hours)
128
+ // By subtracting 26 hours, MIN_BUNDLE_ID becomes a safe "past timestamp" regardless of build timezone
129
+ // Example: Build at 15:00 in any timezone → parse as 15:00 UTC → subtract 26h → 13:00 UTC (previous day)
130
+ NSTimeInterval adjustedTimestamp = [buildDate timeIntervalSince1970] - 93600.0;
131
+ uint64_t buildTimestampMs = (uint64_t)(adjustedTimestamp * 1000.0);
132
+
133
+ uuid = [self generateUUIDv7FromTimestamp:buildTimestampMs];
101
134
  #endif
102
135
  });
103
136
  return uuid;
104
137
  }
105
138
 
139
+ // Helper method: Generate UUID v7 from timestamp (milliseconds)
140
+ - (NSString *)generateUUIDv7FromTimestamp:(uint64_t)timestampMs {
141
+ unsigned char bytes[16];
142
+
143
+ // UUID v7 format: timestamp_ms (48 bits) + ver (4 bits) + random (12 bits) + variant (2 bits) + random (62 bits)
144
+ bytes[0] = (timestampMs >> 40) & 0xFF;
145
+ bytes[1] = (timestampMs >> 32) & 0xFF;
146
+ bytes[2] = (timestampMs >> 24) & 0xFF;
147
+ bytes[3] = (timestampMs >> 16) & 0xFF;
148
+ bytes[4] = (timestampMs >> 8) & 0xFF;
149
+ bytes[5] = timestampMs & 0xFF;
150
+
151
+ // Version 7
152
+ bytes[6] = 0x70;
153
+ bytes[7] = 0x00;
154
+
155
+ // Variant bits (10xxxxxx)
156
+ bytes[8] = 0x80;
157
+ bytes[9] = 0x00;
158
+
159
+ // Remaining bytes (zeros for deterministic MIN_BUNDLE_ID)
160
+ for (int i = 10; i < 16; i++) {
161
+ bytes[i] = 0x00;
162
+ }
106
163
 
107
- - (NSDictionary *)constantsToExport {
108
- return @{
109
- @"MIN_BUNDLE_ID": [self getMinBundleId] ?: [NSNull null], // Local
110
- @"APP_VERSION": [HotUpdaterImpl appVersion] ?: [NSNull null], // Swift
111
- @"CHANNEL": [_hotUpdaterImpl getChannel] ?: [NSNull null], // Swift
112
- @"FINGERPRINT_HASH": [_hotUpdaterImpl getFingerprintHash] ?: [NSNull null] // Swift
113
- };
164
+ return [NSString stringWithFormat:
165
+ @"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
166
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
167
+ bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]];
114
168
  }
115
169
 
116
- - (NSDictionary *)getConstants {
117
- return [self constantsToExport];
170
+
171
+ - (NSDictionary *)_buildConstantsDictionary {
172
+ return @{
173
+ @"MIN_BUNDLE_ID": [self getMinBundleId] ?: [NSNull null],
174
+ @"APP_VERSION": [HotUpdaterImpl appVersion] ?: [NSNull null],
175
+ @"CHANNEL": [[HotUpdater sharedImpl] getChannel] ?: [NSNull null],
176
+ @"FINGERPRINT_HASH": [[HotUpdater sharedImpl] getFingerprintHash] ?: [NSNull null]
177
+ };
118
178
  }
119
179
 
120
180
 
121
- // Get bundleURL using static instance
181
+ // Get bundleURL using singleton
122
182
  + (NSURL *)bundleURL {
123
- return [_hotUpdaterImpl bundleURL];
183
+ return [[HotUpdater sharedImpl] bundleURL];
184
+ }
185
+
186
+ // Get bundleURL using instance impl
187
+ - (NSURL *)bundleURL {
188
+ return [[HotUpdater sharedImpl] bundleURL];
124
189
  }
125
190
 
126
191
 
@@ -177,20 +242,21 @@ RCT_EXPORT_MODULE();
177
242
  }
178
243
 
179
244
 
180
- #pragma mark - React Native Exports (Slimmed Down)
245
+ #pragma mark - React Native Exports
181
246
 
182
- // Keep reload logic here as it interacts with RN Bridge
183
- RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve
184
- reject:(RCTPromiseRejectBlock)reject) {
247
+ #ifdef RCT_NEW_ARCH_ENABLED
248
+
249
+ // New Architecture implementations
250
+
251
+ - (void)reload:(RCTPromiseResolveBlock)resolve
252
+ reject:(RCTPromiseRejectBlock)reject {
185
253
  RCTLogInfo(@"[HotUpdater.mm] HotUpdater requested a reload");
186
254
  dispatch_async(dispatch_get_main_queue(), ^{
187
255
  @try {
188
- // Get bundleURL using static instance
189
- NSURL *bundleURL = [_hotUpdaterImpl bundleURL];
256
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
257
+ NSURL *bundleURL = [impl bundleURL];
190
258
  RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
191
259
  if (bundleURL && super.bridge) {
192
- // This method of setting bundleURL might be outdated depending on RN version.
193
- // Consider alternatives if this doesn't work reliably.
194
260
  [super.bridge setValue:bundleURL forKey:@"bundleURL"];
195
261
  } else if (!super.bridge) {
196
262
  RCTLogWarn(@"[HotUpdater.mm] Bridge is nil, cannot set bundleURL for reload.");
@@ -204,11 +270,9 @@ RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve
204
270
  });
205
271
  }
206
272
 
207
- #ifdef RCT_NEW_ARCH_ENABLED
208
-
209
- RCT_EXPORT_METHOD(updateBundle:(JS::NativeHotUpdater::UpdateBundleParams &)params
210
- resolve:(RCTPromiseResolveBlock)resolve
211
- reject:(RCTPromiseRejectBlock)reject) {
273
+ - (void)updateBundle:(JS::NativeHotUpdater::UpdateBundleParams &)params
274
+ resolve:(RCTPromiseResolveBlock)resolve
275
+ reject:(RCTPromiseRejectBlock)reject {
212
276
  NSLog(@"[HotUpdater.mm] updateBundle called.");
213
277
  NSMutableDictionary *paramDict = [NSMutableDictionary dictionary];
214
278
  if (params.bundleId()) {
@@ -221,18 +285,120 @@ RCT_EXPORT_METHOD(updateBundle:(JS::NativeHotUpdater::UpdateBundleParams &)param
221
285
  paramDict[@"fileHash"] = params.fileHash();
222
286
  }
223
287
 
224
- [_hotUpdaterImpl updateBundle:paramDict resolver:resolve rejecter:reject];
288
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
289
+ [impl updateBundle:paramDict resolver:resolve rejecter:reject];
290
+ }
291
+
292
+ - (NSDictionary *)notifyAppReady:(JS::NativeHotUpdater::SpecNotifyAppReadyParams &)params {
293
+ NSString *bundleId = nil;
294
+ if (params.bundleId()) {
295
+ bundleId = params.bundleId();
296
+ }
297
+ NSLog(@"[HotUpdater.mm] notifyAppReady called with bundleId: %@", bundleId);
298
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
299
+ return [impl notifyAppReadyWithBundleId:bundleId];
300
+ }
301
+
302
+ - (NSArray<NSString *> *)getCrashHistory {
303
+ NSLog(@"[HotUpdater.mm] getCrashHistory called");
304
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
305
+ NSArray<NSString *> *crashHistory = [impl getCrashHistory];
306
+ return crashHistory ?: @[];
307
+ }
308
+
309
+ - (NSNumber *)clearCrashHistory {
310
+ NSLog(@"[HotUpdater.mm] clearCrashHistory called");
311
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
312
+ BOOL result = [impl clearCrashHistory];
313
+ return @(result);
314
+ }
315
+
316
+ - (void)addListener:(NSString *)eventName {
317
+ // No-op for New Architecture - handled by event emitter
318
+ }
319
+
320
+ - (void)removeListeners:(double)count {
321
+ // No-op for New Architecture - handled by event emitter
322
+ }
323
+
324
+ - (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)constantsToExport {
325
+ return [self getConstants];
326
+ }
327
+
328
+ - (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)getConstants {
329
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
330
+ return facebook::react::typedConstants<JS::NativeHotUpdater::Constants::Builder>({
331
+ .MIN_BUNDLE_ID = [self getMinBundleId],
332
+ .APP_VERSION = [HotUpdaterImpl appVersion],
333
+ .CHANNEL = [impl getChannel],
334
+ .FINGERPRINT_HASH = [impl getFingerprintHash],
335
+ });
225
336
  }
337
+
226
338
  #else
339
+
340
+ // Old Architecture implementations
341
+
342
+ RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve
343
+ reject:(RCTPromiseRejectBlock)reject) {
344
+ RCTLogInfo(@"[HotUpdater.mm] HotUpdater requested a reload");
345
+ dispatch_async(dispatch_get_main_queue(), ^{
346
+ @try {
347
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
348
+ NSURL *bundleURL = [impl bundleURL];
349
+ RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
350
+ if (bundleURL && super.bridge) {
351
+ [super.bridge setValue:bundleURL forKey:@"bundleURL"];
352
+ } else if (!super.bridge) {
353
+ RCTLogWarn(@"[HotUpdater.mm] Bridge is nil, cannot set bundleURL for reload.");
354
+ }
355
+ RCTTriggerReloadCommandListeners(@"HotUpdater requested a reload");
356
+ resolve(nil);
357
+ } @catch (NSError *error) {
358
+ RCTLogError(@"[HotUpdater.mm] Failed to reload: %@", error);
359
+ reject(@"RELOAD_ERROR", error.description, error);
360
+ }
361
+ });
362
+ }
363
+
227
364
  RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
228
365
  resolve:(RCTPromiseResolveBlock)resolve
229
366
  reject:(RCTPromiseRejectBlock)reject) {
230
367
  NSLog(@"[HotUpdater.mm] updateBundle called. params: %@", params);
231
- [_hotUpdaterImpl updateBundle:params resolver:resolve rejecter:reject];
368
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
369
+ [impl updateBundle:params resolver:resolve rejecter:reject];
232
370
  }
233
- #endif
234
371
 
372
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(notifyAppReady:(NSDictionary *)params) {
373
+ NSString *bundleId = params[@"bundleId"];
374
+ NSLog(@"[HotUpdater.mm] notifyAppReady called with bundleId: %@", bundleId);
375
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
376
+ return [impl notifyAppReadyWithBundleId:bundleId];
377
+ }
235
378
 
379
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCrashHistory) {
380
+ NSLog(@"[HotUpdater.mm] getCrashHistory called");
381
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
382
+ NSArray<NSString *> *crashHistory = [impl getCrashHistory];
383
+ return crashHistory ?: @[];
384
+ }
385
+
386
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(clearCrashHistory) {
387
+ NSLog(@"[HotUpdater.mm] clearCrashHistory called");
388
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
389
+ BOOL result = [impl clearCrashHistory];
390
+ return @(result);
391
+ }
392
+
393
+ - (NSDictionary *)constantsToExport {
394
+ return [self _buildConstantsDictionary];
395
+ }
396
+
397
+ - (NSDictionary *)getConstants {
398
+ return [self constantsToExport];
399
+ }
400
+
401
+ #endif
236
402
 
237
403
 
238
404
  #pragma mark - Turbo Module Support (Keep as is)