@hot-updater/react-native 0.23.1 → 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.
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +393 -49
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +204 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +48 -36
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +134 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +168 -95
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +15 -3
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +17 -12
- package/android/src/newarch/HotUpdaterModule.kt +88 -23
- package/android/src/oldarch/HotUpdaterModule.kt +89 -22
- package/android/src/oldarch/HotUpdaterSpec.kt +6 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +401 -77
- package/ios/HotUpdater/Internal/BundleMetadata.swift +177 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +213 -47
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +96 -25
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +35 -29
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +2 -2
- package/ios/HotUpdater/Public/HotUpdater.h +8 -2
- package/lib/commonjs/checkForUpdate.js +31 -28
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/error.js +45 -1
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchUpdateInfo.js +7 -45
- package/lib/commonjs/fetchUpdateInfo.js.map +1 -1
- package/lib/commonjs/index.js +237 -208
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +103 -3
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/wrap.js +39 -1
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/checkForUpdate.js +32 -26
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/error.js +45 -0
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchUpdateInfo.js +7 -45
- package/lib/module/fetchUpdateInfo.js.map +1 -1
- package/lib/module/index.js +238 -203
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +87 -2
- package/lib/module/native.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/wrap.js +40 -2
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +11 -13
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/error.d.ts +120 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +3 -5
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +35 -41
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +58 -2
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +62 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +76 -5
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +11 -13
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/error.d.ts +120 -0
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchUpdateInfo.d.ts +3 -5
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +35 -41
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +58 -2
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +62 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +76 -5
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +7 -7
- package/plugin/build/withHotUpdater.js +3 -3
- package/src/checkForUpdate.ts +51 -40
- package/src/error.ts +153 -0
- package/src/fetchUpdateInfo.ts +10 -58
- package/src/index.ts +283 -206
- package/src/native.ts +88 -2
- package/src/specs/NativeHotUpdater.ts +63 -0
- package/src/wrap.tsx +131 -9
- package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +0 -52
- package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +0 -24
- package/lib/commonjs/runUpdateProcess.js +0 -69
- package/lib/commonjs/runUpdateProcess.js.map +0 -1
- package/lib/module/runUpdateProcess.js +0 -64
- package/lib/module/runUpdateProcess.js.map +0 -1
- package/lib/typescript/commonjs/runUpdateProcess.d.ts +0 -49
- package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +0 -1
- package/lib/typescript/module/runUpdateProcess.d.ts +0 -49
- package/lib/typescript/module/runUpdateProcess.d.ts.map +0 -1
- 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];
|
|
36
|
+
object:nil];
|
|
41
37
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
42
38
|
selector:@selector(handleDownloadCompletion:)
|
|
43
39
|
name:HotUpdaterDownloadDidFinishNotification
|
|
44
|
-
object:nil];
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
|
181
|
+
// Get bundleURL using singleton
|
|
122
182
|
+ (NSURL *)bundleURL {
|
|
123
|
-
return [
|
|
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
|
|
245
|
+
#pragma mark - React Native Exports
|
|
181
246
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
189
|
-
NSURL *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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|