@hot-updater/react-native 0.27.1 → 0.29.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 (87) hide show
  1. package/android/build.gradle +12 -0
  2. package/android/src/main/AndroidManifest.xml +3 -0
  3. package/android/src/main/AndroidManifestNew.xml +3 -0
  4. package/android/src/main/cpp/CMakeLists.txt +9 -0
  5. package/android/src/main/cpp/HotUpdaterRecovery.cpp +143 -0
  6. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +325 -210
  7. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +73 -16
  8. package/android/src/main/java/com/hotupdater/CohortService.kt +73 -0
  9. package/android/src/main/java/com/hotupdater/DecompressService.kt +28 -22
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +1 -1
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +51 -13
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +533 -0
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryReceiver.kt +14 -0
  14. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +55 -0
  15. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +19 -7
  16. package/android/src/newarch/HotUpdaterModule.kt +16 -25
  17. package/android/src/oldarch/HotUpdaterModule.kt +20 -26
  18. package/android/src/oldarch/HotUpdaterSpec.kt +12 -2
  19. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +340 -232
  20. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -8
  21. package/ios/HotUpdater/Internal/CohortService.swift +63 -0
  22. package/ios/HotUpdater/Internal/DecompressService.swift +53 -30
  23. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +9 -1
  24. package/ios/HotUpdater/Internal/HotUpdater.mm +376 -70
  25. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +7 -0
  26. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +4 -0
  27. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +321 -9
  28. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +24 -8
  29. package/lib/commonjs/DefaultResolver.js +3 -5
  30. package/lib/commonjs/DefaultResolver.js.map +1 -1
  31. package/lib/commonjs/checkForUpdate.js +2 -0
  32. package/lib/commonjs/checkForUpdate.js.map +1 -1
  33. package/lib/commonjs/index.js +13 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/native.js +211 -39
  36. package/lib/commonjs/native.js.map +1 -1
  37. package/lib/commonjs/native.spec.js +443 -0
  38. package/lib/commonjs/native.spec.js.map +1 -0
  39. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  40. package/lib/commonjs/types.js.map +1 -1
  41. package/lib/commonjs/wrap.js +4 -5
  42. package/lib/commonjs/wrap.js.map +1 -1
  43. package/lib/module/DefaultResolver.js +3 -5
  44. package/lib/module/DefaultResolver.js.map +1 -1
  45. package/lib/module/checkForUpdate.js +3 -1
  46. package/lib/module/checkForUpdate.js.map +1 -1
  47. package/lib/module/index.js +14 -1
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/native.js +204 -34
  50. package/lib/module/native.js.map +1 -1
  51. package/lib/module/native.spec.js +442 -0
  52. package/lib/module/native.spec.js.map +1 -0
  53. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  54. package/lib/module/types.js.map +1 -1
  55. package/lib/module/wrap.js +5 -6
  56. package/lib/module/wrap.js.map +1 -1
  57. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/index.d.ts +14 -1
  59. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/native.d.ts +43 -23
  61. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +32 -8
  63. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/types.d.ts +6 -3
  65. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/wrap.d.ts +3 -6
  67. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  68. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  69. package/lib/typescript/module/index.d.ts +14 -1
  70. package/lib/typescript/module/index.d.ts.map +1 -1
  71. package/lib/typescript/module/native.d.ts +43 -23
  72. package/lib/typescript/module/native.d.ts.map +1 -1
  73. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +32 -8
  74. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  75. package/lib/typescript/module/types.d.ts +6 -3
  76. package/lib/typescript/module/types.d.ts.map +1 -1
  77. package/lib/typescript/module/wrap.d.ts +3 -6
  78. package/lib/typescript/module/wrap.d.ts.map +1 -1
  79. package/package.json +6 -6
  80. package/src/DefaultResolver.ts +4 -4
  81. package/src/checkForUpdate.ts +4 -0
  82. package/src/index.ts +21 -0
  83. package/src/native.spec.ts +480 -0
  84. package/src/native.ts +285 -39
  85. package/src/specs/NativeHotUpdater.ts +36 -6
  86. package/src/types.ts +7 -3
  87. package/src/wrap.tsx +8 -12
@@ -1,7 +1,13 @@
1
1
  #import "HotUpdater.h"
2
+ #import <React/RCTExceptionsManager.h>
3
+ #import <React/RCTInitializing.h>
2
4
  #import <React/RCTReloadCommand.h>
3
5
  #import <React/RCTLog.h>
4
6
 
7
+ #include <fcntl.h>
8
+ #include <limits.h>
9
+ #include <signal.h>
10
+ #include <unistd.h>
5
11
 
6
12
  #if __has_include("HotUpdater/HotUpdater-Swift.h")
7
13
  #import "HotUpdater/HotUpdater-Swift.h"
@@ -9,17 +15,259 @@
9
15
  #import "HotUpdater-Swift.h"
10
16
  #endif
11
17
 
18
+ @interface HotUpdater (InternalSharedImpl)
19
+ + (HotUpdaterImpl *)sharedImpl;
20
+ + (NSString *)generateUUIDv7FromTimestamp:(uint64_t)timestampMs;
21
+ @end
22
+
23
+ namespace {
24
+ constexpr size_t kHotUpdaterMaxBundleIdLength = 128;
25
+ const int kHotUpdaterSignals[] = {SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP};
26
+
27
+ char gHotUpdaterCrashMarkerPath[PATH_MAX] = {0};
28
+ char gHotUpdaterBundleId[kHotUpdaterMaxBundleIdLength] = {0};
29
+ volatile sig_atomic_t gHotUpdaterShouldRollback = 0;
30
+ struct sigaction gHotUpdaterPreviousActions[NSIG];
31
+ __weak RCTBridge *gHotUpdaterBridge = nil;
32
+
33
+ size_t HotUpdaterSafeStringLength(const char *value, size_t maxLength)
34
+ {
35
+ size_t length = 0;
36
+ while (length < maxLength && value[length] != '\0') {
37
+ ++length;
38
+ }
39
+ return length;
40
+ }
41
+
42
+ void HotUpdaterSafeCopy(char *destination, size_t destinationSize, const char *source)
43
+ {
44
+ if (destinationSize == 0) {
45
+ return;
46
+ }
47
+
48
+ size_t index = 0;
49
+ while (index + 1 < destinationSize && source[index] != '\0') {
50
+ destination[index] = source[index];
51
+ ++index;
52
+ }
53
+ destination[index] = '\0';
54
+ }
55
+
56
+ void HotUpdaterWriteCrashMarker()
57
+ {
58
+ if (gHotUpdaterCrashMarkerPath[0] == '\0') {
59
+ return;
60
+ }
61
+
62
+ int fd = open(gHotUpdaterCrashMarkerPath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
63
+ if (fd < 0) {
64
+ return;
65
+ }
66
+
67
+ constexpr char prefix[] = "{\"bundleId\":\"";
68
+ constexpr char middle[] = "\",\"shouldRollback\":";
69
+ constexpr char trueLiteral[] = "true";
70
+ constexpr char falseLiteral[] = "false";
71
+ constexpr char suffix[] = "}\n";
72
+
73
+ write(fd, prefix, sizeof(prefix) - 1);
74
+ if (gHotUpdaterBundleId[0] != '\0') {
75
+ write(fd, gHotUpdaterBundleId, HotUpdaterSafeStringLength(gHotUpdaterBundleId, kHotUpdaterMaxBundleIdLength));
76
+ }
77
+ write(fd, middle, sizeof(middle) - 1);
78
+ if (gHotUpdaterShouldRollback != 0) {
79
+ write(fd, trueLiteral, sizeof(trueLiteral) - 1);
80
+ } else {
81
+ write(fd, falseLiteral, sizeof(falseLiteral) - 1);
82
+ }
83
+ write(fd, suffix, sizeof(suffix) - 1);
84
+ close(fd);
85
+ }
86
+
87
+ void HotUpdaterSignalHandler(int signum, siginfo_t *info, void *context);
88
+
89
+ void HotUpdaterForwardToPreviousHandler(int signum, siginfo_t *info, void *context)
90
+ {
91
+ const struct sigaction &previousAction = gHotUpdaterPreviousActions[signum];
92
+
93
+ if ((previousAction.sa_flags & SA_SIGINFO) != 0 && previousAction.sa_sigaction != nullptr &&
94
+ previousAction.sa_sigaction != HotUpdaterSignalHandler) {
95
+ previousAction.sa_sigaction(signum, info, context);
96
+ return;
97
+ }
98
+
99
+ if (previousAction.sa_handler == SIG_IGN) {
100
+ return;
101
+ }
102
+
103
+ if (previousAction.sa_handler != nullptr && previousAction.sa_handler != SIG_DFL &&
104
+ previousAction.sa_handler != SIG_ERR) {
105
+ previousAction.sa_handler(signum);
106
+ return;
107
+ }
108
+
109
+ struct sigaction defaultAction {};
110
+ defaultAction.sa_handler = SIG_DFL;
111
+ sigemptyset(&defaultAction.sa_mask);
112
+ sigaction(signum, &defaultAction, nullptr);
113
+ raise(signum);
114
+ }
115
+
116
+ void HotUpdaterSignalHandler(int signum, siginfo_t *info, void *context)
117
+ {
118
+ HotUpdaterWriteCrashMarker();
119
+ HotUpdaterForwardToPreviousHandler(signum, info, context);
120
+ }
121
+ } // namespace
122
+
123
+ extern "C" void HotUpdaterInstallSignalHandlers(NSString *crashMarkerPath)
124
+ {
125
+ HotUpdaterSafeCopy(gHotUpdaterCrashMarkerPath, sizeof(gHotUpdaterCrashMarkerPath), crashMarkerPath.UTF8String ?: "");
126
+
127
+ struct sigaction action {};
128
+ action.sa_sigaction = HotUpdaterSignalHandler;
129
+ action.sa_flags = SA_SIGINFO | SA_ONSTACK;
130
+ sigemptyset(&action.sa_mask);
131
+
132
+ for (int signum : kHotUpdaterSignals) {
133
+ sigaction(signum, &action, &gHotUpdaterPreviousActions[signum]);
134
+ }
135
+ }
136
+
137
+ extern "C" void HotUpdaterUpdateSignalLaunchState(NSString * _Nullable bundleId, BOOL shouldRollback)
138
+ {
139
+ HotUpdaterSafeCopy(gHotUpdaterBundleId, sizeof(gHotUpdaterBundleId), bundleId.UTF8String ?: "");
140
+ gHotUpdaterShouldRollback = shouldRollback ? 1 : 0;
141
+ }
142
+
143
+ extern "C" BOOL HotUpdaterPerformRecoveryReload(void)
144
+ {
145
+ __block BOOL didTriggerReload = NO;
146
+
147
+ void (^reloadBlock)(void) = ^{
148
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
149
+ [impl resetLaunchPreparation];
150
+
151
+ NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
152
+ if (!bundleURL) {
153
+ RCTLogWarn(@"[HotUpdater.mm] Failed to resolve bundle URL for recovery reload");
154
+ return;
155
+ }
156
+
157
+ RCTReloadCommandSetBundleURL(bundleURL);
158
+ RCTBridge *bridge = gHotUpdaterBridge;
159
+ if (bridge) {
160
+ [bridge setValue:bundleURL forKey:@"bundleURL"];
161
+ }
162
+ RCTTriggerReloadCommandListeners(@"HotUpdater recovery reload");
163
+ didTriggerReload = YES;
164
+ };
165
+
166
+ if ([NSThread isMainThread]) {
167
+ reloadBlock();
168
+ } else {
169
+ dispatch_sync(dispatch_get_main_queue(), reloadBlock);
170
+ }
171
+
172
+ return didTriggerReload;
173
+ }
174
+
175
+ extern "C" NSString *HotUpdaterGetMinBundleId(void)
176
+ {
177
+ static NSString *uuid = nil;
178
+ static dispatch_once_t onceToken;
179
+ dispatch_once(&onceToken, ^{
180
+ #if DEBUG
181
+ uuid = @"00000000-0000-0000-0000-000000000000";
182
+ #else
183
+ // Step 1: Try to read HOT_UPDATER_BUILD_TIMESTAMP from Info.plist
184
+ NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
185
+ NSString *customValue = infoDictionary[@"HOT_UPDATER_BUILD_TIMESTAMP"];
186
+
187
+ // Step 2: If custom value exists and is not empty
188
+ if (customValue && customValue.length > 0 && ![customValue isEqualToString:@"$(HOT_UPDATER_BUILD_TIMESTAMP)"]) {
189
+ // Check if it's a timestamp (pure digits) or UUID
190
+ NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
191
+ BOOL isTimestamp = ([customValue rangeOfCharacterFromSet:nonDigits].location == NSNotFound);
192
+
193
+ if (isTimestamp) {
194
+ // Convert timestamp (milliseconds) to UUID v7
195
+ uint64_t timestampMs = [customValue longLongValue];
196
+ uuid = [HotUpdater generateUUIDv7FromTimestamp:timestampMs];
197
+ RCTLogInfo(@"[HotUpdater.mm] Using timestamp %@ as MIN_BUNDLE_ID: %@", customValue, uuid);
198
+ } else {
199
+ // Use as UUID directly
200
+ uuid = customValue;
201
+ RCTLogInfo(@"[HotUpdater.mm] Using custom MIN_BUNDLE_ID from Info.plist: %@", uuid);
202
+ }
203
+ return;
204
+ }
205
+
206
+ // Step 3: Fallback to default logic (26-hour subtraction)
207
+ RCTLogInfo(@"[HotUpdater.mm] No custom MIN_BUNDLE_ID found, using default calculation");
208
+
209
+ NSString *compileDateStr = [NSString stringWithFormat:@"%s %s", __DATE__, __TIME__];
210
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
211
+
212
+ // Parse __DATE__ __TIME__ as UTC to ensure consistent timezone handling across all build environments
213
+ [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
214
+ [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
215
+ [formatter setDateFormat:@"MMM d yyyy HH:mm:ss"];
216
+ NSDate *buildDate = [formatter dateFromString:compileDateStr];
217
+ if (!buildDate) {
218
+ RCTLogWarn(@"[HotUpdater.mm] Could not parse build date: %@", compileDateStr);
219
+ uuid = @"00000000-0000-0000-0000-000000000000";
220
+ return;
221
+ }
222
+
223
+ // Subtract 26 hours (93600 seconds) to ensure MIN_BUNDLE_ID is always in the past
224
+ // This guarantees that uuidv7-based bundleIds (generated at runtime) will always be newer than MIN_BUNDLE_ID
225
+ // Why 26 hours? Global timezone range spans from UTC-12 to UTC+14 (total 26 hours)
226
+ // By subtracting 26 hours, MIN_BUNDLE_ID becomes a safe "past timestamp" regardless of build timezone
227
+ // Example: Build at 15:00 in any timezone -> parse as 15:00 UTC -> subtract 26h -> 13:00 UTC (previous day)
228
+ NSTimeInterval adjustedTimestamp = [buildDate timeIntervalSince1970] - 93600.0;
229
+ uint64_t buildTimestampMs = (uint64_t)(adjustedTimestamp * 1000.0);
230
+
231
+ uuid = [HotUpdater generateUUIDv7FromTimestamp:buildTimestampMs];
232
+ #endif
233
+ });
234
+ return uuid;
235
+ }
236
+
12
237
 
13
238
  // Define Notification names used for observing Swift Core
14
239
  NSNotificationName const HotUpdaterDownloadProgressUpdateNotification = @"HotUpdaterDownloadProgressUpdate";
15
240
  NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterDownloadDidFinish";
16
241
 
242
+ @interface HotUpdaterRecoverySignalBridge : NSObject
243
+ @end
244
+
245
+ @implementation HotUpdaterRecoverySignalBridge
246
+
247
+ + (void)installSignalHandlers:(NSString *)crashMarkerPath
248
+ {
249
+ HotUpdaterInstallSignalHandlers(crashMarkerPath);
250
+ }
251
+
252
+ + (void)updateLaunchState:(NSString * _Nullable)bundleId shouldRollback:(BOOL)shouldRollback
253
+ {
254
+ HotUpdaterUpdateSignalLaunchState(bundleId, shouldRollback);
255
+ }
256
+
257
+ @end
258
+
259
+ @interface HotUpdater () <RCTInitializing>
260
+ @end
261
+
17
262
  @implementation HotUpdater {
18
263
  bool hasListeners;
19
264
  // Keep track of tasks ONLY for removing observers when this ObjC instance is invalidated
20
265
  NSMutableSet<NSURLSessionTask *> *observedTasks; // Changed to NSURLSessionTask for broader compatibility if needed
21
266
  }
22
267
 
268
+ @synthesize bridge = _bridge;
269
+ @synthesize moduleRegistry = _moduleRegistry;
270
+
23
271
  + (BOOL)requiresMainQueueSetup {
24
272
  return YES;
25
273
  }
@@ -43,10 +291,25 @@ NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterD
43
291
  return self;
44
292
  }
45
293
 
294
+ - (void)initialize
295
+ {
296
+ [self configureExceptionsManagerWithModuleRegistry:self.moduleRegistry];
297
+ }
298
+
299
+ - (void)setBridge:(RCTBridge *)bridge
300
+ {
301
+ [super setBridge:bridge];
302
+ gHotUpdaterBridge = bridge;
303
+ [self configureExceptionsManagerWithBridge:bridge];
304
+ }
305
+
46
306
  // Clean up observers when module is invalidated or deallocated
47
307
  - (void)invalidate {
48
308
  RCTLogInfo(@"[HotUpdater.mm] invalidate called, removing observers.");
49
309
  [[NSNotificationCenter defaultCenter] removeObserver:self];
310
+ if (gHotUpdaterBridge == self.bridge) {
311
+ gHotUpdaterBridge = nil;
312
+ }
50
313
  // Swift side should handle KVO observer removal for its tasks
51
314
  [super invalidate];
52
315
  }
@@ -54,6 +317,57 @@ NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterD
54
317
  - (void)dealloc {
55
318
  RCTLogInfo(@"[HotUpdater.mm] dealloc called, removing observers.");
56
319
  [[NSNotificationCenter defaultCenter] removeObserver:self];
320
+ if (gHotUpdaterBridge == self.bridge) {
321
+ gHotUpdaterBridge = nil;
322
+ }
323
+ }
324
+
325
+ - (void)configureExceptionsManagerWithBridge:(RCTBridge *)bridge
326
+ {
327
+ if (!bridge) {
328
+ return;
329
+ }
330
+
331
+ id exceptionsManager = [bridge moduleForClass:[RCTExceptionsManager class]];
332
+ [self applyExceptionsManagerReloadLimit:exceptionsManager];
333
+ }
334
+
335
+ - (void)configureExceptionsManagerWithModuleRegistry:(id)moduleRegistry
336
+ {
337
+ if (!moduleRegistry || ![moduleRegistry respondsToSelector:@selector(moduleForName:lazilyLoadIfNecessary:)]) {
338
+ return;
339
+ }
340
+
341
+ #pragma clang diagnostic push
342
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
343
+ SEL selector = @selector(moduleForName:lazilyLoadIfNecessary:);
344
+ NSMethodSignature *signature = [moduleRegistry methodSignatureForSelector:selector];
345
+ if (!signature) {
346
+ return;
347
+ }
348
+
349
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
350
+ invocation.target = moduleRegistry;
351
+ invocation.selector = selector;
352
+
353
+ const char *moduleName = "ExceptionsManager";
354
+ BOOL lazilyLoad = YES;
355
+ [invocation setArgument:&moduleName atIndex:2];
356
+ [invocation setArgument:&lazilyLoad atIndex:3];
357
+ [invocation invoke];
358
+
359
+ __unsafe_unretained id exceptionsManager = nil;
360
+ [invocation getReturnValue:&exceptionsManager];
361
+ #pragma clang diagnostic pop
362
+
363
+ [self applyExceptionsManagerReloadLimit:exceptionsManager];
364
+ }
365
+
366
+ - (void)applyExceptionsManagerReloadLimit:(id)exceptionsManager
367
+ {
368
+ if ([exceptionsManager isKindOfClass:[RCTExceptionsManager class]]) {
369
+ ((RCTExceptionsManager *)exceptionsManager).maxReloadAttempts = 0;
370
+ }
57
371
  }
58
372
 
59
373
 
@@ -75,68 +389,11 @@ RCT_EXPORT_MODULE();
75
389
 
76
390
  // Returns the minimum bundle ID string, either from Info.plist or generated from build timestamp
77
391
  - (NSString *)getMinBundleId {
78
- static NSString *uuid = nil;
79
- static dispatch_once_t onceToken;
80
- dispatch_once(&onceToken, ^{
81
- #if DEBUG
82
- uuid = @"00000000-0000-0000-0000-000000000000";
83
- #else
84
- // Step 1: Try to read HOT_UPDATER_BUILD_TIMESTAMP from Info.plist
85
- NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
86
- NSString *customValue = infoDictionary[@"HOT_UPDATER_BUILD_TIMESTAMP"];
87
-
88
- // Step 2: If custom value exists and is not empty
89
- if (customValue && customValue.length > 0 && ![customValue isEqualToString:@"$(HOT_UPDATER_BUILD_TIMESTAMP)"]) {
90
- // Check if it's a timestamp (pure digits) or UUID
91
- NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
92
- BOOL isTimestamp = ([customValue rangeOfCharacterFromSet:nonDigits].location == NSNotFound);
93
-
94
- if (isTimestamp) {
95
- // Convert timestamp (milliseconds) to UUID v7
96
- uint64_t timestampMs = [customValue longLongValue];
97
- uuid = [self generateUUIDv7FromTimestamp:timestampMs];
98
- RCTLogInfo(@"[HotUpdater.mm] Using timestamp %@ as MIN_BUNDLE_ID: %@", customValue, uuid);
99
- } else {
100
- // Use as UUID directly
101
- uuid = customValue;
102
- RCTLogInfo(@"[HotUpdater.mm] Using custom MIN_BUNDLE_ID from Info.plist: %@", uuid);
103
- }
104
- return;
105
- }
106
-
107
- // Step 3: Fallback to default logic (26-hour subtraction)
108
- RCTLogInfo(@"[HotUpdater.mm] No custom MIN_BUNDLE_ID found, using default calculation");
109
-
110
- NSString *compileDateStr = [NSString stringWithFormat:@"%s %s", __DATE__, __TIME__];
111
- NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
112
-
113
- // Parse __DATE__ __TIME__ as UTC to ensure consistent timezone handling across all build environments
114
- [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
115
- [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
116
- [formatter setDateFormat:@"MMM d yyyy HH:mm:ss"]; // Correct format for __DATE__ __TIME__
117
- NSDate *buildDate = [formatter dateFromString:compileDateStr];
118
- if (!buildDate) {
119
- RCTLogWarn(@"[HotUpdater.mm] Could not parse build date: %@", compileDateStr);
120
- uuid = @"00000000-0000-0000-0000-000000000000";
121
- return;
122
- }
123
-
124
- // Subtract 26 hours (93600 seconds) to ensure MIN_BUNDLE_ID is always in the past
125
- // This guarantees that uuidv7-based bundleIds (generated at runtime) will always be newer than MIN_BUNDLE_ID
126
- // Why 26 hours? Global timezone range spans from UTC-12 to UTC+14 (total 26 hours)
127
- // By subtracting 26 hours, MIN_BUNDLE_ID becomes a safe "past timestamp" regardless of build timezone
128
- // Example: Build at 15:00 in any timezone → parse as 15:00 UTC → subtract 26h → 13:00 UTC (previous day)
129
- NSTimeInterval adjustedTimestamp = [buildDate timeIntervalSince1970] - 93600.0;
130
- uint64_t buildTimestampMs = (uint64_t)(adjustedTimestamp * 1000.0);
131
-
132
- uuid = [self generateUUIDv7FromTimestamp:buildTimestampMs];
133
- #endif
134
- });
135
- return uuid;
392
+ return HotUpdaterGetMinBundleId();
136
393
  }
137
394
 
138
395
  // Helper method: Generate UUID v7 from timestamp (milliseconds)
139
- - (NSString *)generateUUIDv7FromTimestamp:(uint64_t)timestampMs {
396
+ + (NSString *)generateUUIDv7FromTimestamp:(uint64_t)timestampMs {
140
397
  unsigned char bytes[16];
141
398
 
142
399
  // UUID v7 format: timestamp_ms (48 bits) + ver (4 bits) + random (12 bits) + variant (2 bits) + random (62 bits)
@@ -257,8 +514,12 @@ RCT_EXPORT_MODULE();
257
514
  dispatch_async(dispatch_get_main_queue(), ^{
258
515
  @try {
259
516
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
517
+ [impl resetLaunchPreparation];
260
518
  NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
261
519
  RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
520
+ if (bundleURL) {
521
+ RCTReloadCommandSetBundleURL(bundleURL);
522
+ }
262
523
  if (bundleURL && super.bridge) {
263
524
  [super.bridge setValue:bundleURL forKey:@"bundleURL"];
264
525
  } else if (!super.bridge) {
@@ -300,14 +561,10 @@ RCT_EXPORT_MODULE();
300
561
  [impl updateBundle:paramDict resolver:resolve rejecter:reject];
301
562
  }
302
563
 
303
- - (NSDictionary *)notifyAppReady:(JS::NativeHotUpdater::SpecNotifyAppReadyParams &)params {
304
- NSString *bundleId = nil;
305
- if (params.bundleId()) {
306
- bundleId = params.bundleId();
307
- }
308
- NSLog(@"[HotUpdater.mm] notifyAppReady called with bundleId: %@", bundleId);
564
+ - (NSDictionary *)notifyAppReady {
565
+ NSLog(@"[HotUpdater.mm] notifyAppReady called");
309
566
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
310
- return [impl notifyAppReadyWithBundleId:bundleId];
567
+ return [impl notifyAppReady];
311
568
  }
312
569
 
313
570
  - (NSArray<NSString *> *)getCrashHistory {
@@ -331,6 +588,29 @@ RCT_EXPORT_MODULE();
331
588
  return baseURL ?: @"";
332
589
  }
333
590
 
591
+ - (void)setCohort:(NSString *)customId {
592
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
593
+ [impl setCohort:customId];
594
+ }
595
+
596
+ - (NSString *)getCohort {
597
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
598
+ return [impl getCohort];
599
+ }
600
+
601
+ - (NSString * _Nullable)getBundleId {
602
+ NSLog(@"[HotUpdater.mm] getBundleId called");
603
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
604
+ return [impl getBundleId];
605
+ }
606
+
607
+ - (NSDictionary<NSString *, id> *)getManifest {
608
+ NSLog(@"[HotUpdater.mm] getManifest called");
609
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
610
+ NSDictionary<NSString *, id> *manifest = [impl getManifest];
611
+ return manifest ?: @{};
612
+ }
613
+
334
614
  - (void)resetChannel:(RCTPromiseResolveBlock)resolve
335
615
  reject:(RCTPromiseRejectBlock)reject {
336
616
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
@@ -362,8 +642,12 @@ RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve
362
642
  dispatch_async(dispatch_get_main_queue(), ^{
363
643
  @try {
364
644
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
645
+ [impl resetLaunchPreparation];
365
646
  NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
366
647
  RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
648
+ if (bundleURL) {
649
+ RCTReloadCommandSetBundleURL(bundleURL);
650
+ }
367
651
  if (bundleURL && super.bridge) {
368
652
  [super.bridge setValue:bundleURL forKey:@"bundleURL"];
369
653
  } else if (!super.bridge) {
@@ -391,11 +675,10 @@ RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
391
675
  [impl updateBundle:params resolver:resolve rejecter:reject];
392
676
  }
393
677
 
394
- RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(notifyAppReady:(NSDictionary *)params) {
395
- NSString *bundleId = params[@"bundleId"];
396
- NSLog(@"[HotUpdater.mm] notifyAppReady called with bundleId: %@", bundleId);
678
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(notifyAppReady) {
679
+ NSLog(@"[HotUpdater.mm] notifyAppReady called");
397
680
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
398
- return [impl notifyAppReadyWithBundleId:bundleId];
681
+ return [impl notifyAppReady];
399
682
  }
400
683
 
401
684
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCrashHistory) {
@@ -419,6 +702,29 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBaseURL) {
419
702
  return baseURL ?: @"";
420
703
  }
421
704
 
705
+ RCT_EXPORT_METHOD(setCohort:(NSString *)customId) {
706
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
707
+ [impl setCohort:customId];
708
+ }
709
+
710
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCohort) {
711
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
712
+ return [impl getCohort];
713
+ }
714
+
715
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBundleId) {
716
+ NSLog(@"[HotUpdater.mm] getBundleId called");
717
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
718
+ return [impl getBundleId];
719
+ }
720
+
721
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getManifest) {
722
+ NSLog(@"[HotUpdater.mm] getManifest called");
723
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
724
+ NSDictionary<NSString *, id> *manifest = [impl getManifest];
725
+ return manifest ?: @{};
726
+ }
727
+
422
728
  RCT_EXPORT_METHOD(resetChannel:(RCTPromiseResolveBlock)resolve
423
729
  reject:(RCTPromiseRejectBlock)reject) {
424
730
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
@@ -0,0 +1,7 @@
1
+ #ifndef HotUpdaterCrashHandler_h
2
+ #define HotUpdaterCrashHandler_h
3
+
4
+ // Compatibility shim for stale CocoaPods projects that still reference the
5
+ // old crash-handler file names before `pod install` refreshes the source list.
6
+
7
+ #endif /* HotUpdaterCrashHandler_h */
@@ -0,0 +1,4 @@
1
+ #import "HotUpdaterCrashHandler.h"
2
+
3
+ // Compatibility shim for stale CocoaPods projects that still compile the old
4
+ // crash-handler translation unit. Recovery logic now lives in `HotUpdater.mm`.