@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.
- package/android/build.gradle +12 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +9 -0
- package/android/src/main/cpp/HotUpdaterRecovery.cpp +143 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +325 -210
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +73 -16
- package/android/src/main/java/com/hotupdater/CohortService.kt +73 -0
- package/android/src/main/java/com/hotupdater/DecompressService.kt +28 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +1 -1
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +51 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +533 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryReceiver.kt +14 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +55 -0
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +19 -7
- package/android/src/newarch/HotUpdaterModule.kt +16 -25
- package/android/src/oldarch/HotUpdaterModule.kt +20 -26
- package/android/src/oldarch/HotUpdaterSpec.kt +12 -2
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +340 -232
- package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -8
- package/ios/HotUpdater/Internal/CohortService.swift +63 -0
- package/ios/HotUpdater/Internal/DecompressService.swift +53 -30
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +9 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +376 -70
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +7 -0
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +4 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +321 -9
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +24 -8
- package/lib/commonjs/DefaultResolver.js +3 -5
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/checkForUpdate.js +2 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +211 -39
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +443 -0
- package/lib/commonjs/native.spec.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/wrap.js +4 -5
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/DefaultResolver.js +3 -5
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/checkForUpdate.js +3 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +14 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +204 -34
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +442 -0
- package/lib/module/native.spec.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/wrap.js +5 -6
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +14 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +43 -23
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +32 -8
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +6 -3
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +3 -6
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +14 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +43 -23
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +32 -8
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +6 -3
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +3 -6
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/DefaultResolver.ts +4 -4
- package/src/checkForUpdate.ts +4 -0
- package/src/index.ts +21 -0
- package/src/native.spec.ts +480 -0
- package/src/native.ts +285 -39
- package/src/specs/NativeHotUpdater.ts +36 -6
- package/src/types.ts +7 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
|
304
|
-
|
|
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
|
|
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
|
|
395
|
-
|
|
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
|
|
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 */
|