@hot-updater/react-native 0.16.7-0 → 0.18.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/HotUpdater.podspec +7 -11
- package/android/{generated/java/com/hotupdater → app/build/generated/source/codegen/java/com/facebook/fbreact/specs}/NativeHotUpdaterSpec.java +3 -2
- package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +68 -0
- package/android/app/build/generated/source/codegen/jni/HotUpdater.h +31 -0
- package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec-generated.cpp +2 -2
- package/android/{generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp → app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI-generated.cpp} +3 -4
- package/{ios/generated/HotUpdaterSpecJSI.h → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h} +53 -6
- package/{ios/generated → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec}/HotUpdaterSpecJSI-generated.cpp +2 -3
- package/android/{generated → app/build/generated/source/codegen}/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +59 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
- package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
- package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
- package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
- package/android/src/newarch/HotUpdaterModule.kt +31 -34
- package/android/src/newarch/ReactIntegrationManager.kt +17 -3
- package/android/src/oldarch/HotUpdaterModule.kt +32 -34
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -2
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
- package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
- package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
- package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
- package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
- package/ios/HotUpdater/Package.resolved +15 -0
- package/ios/HotUpdater/Public/HotUpdater.h +29 -0
- package/lib/commonjs/checkForUpdate.js +70 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -0
- package/lib/commonjs/error.js +14 -0
- package/lib/commonjs/error.js.map +1 -0
- package/lib/commonjs/fetchUpdateInfo.js +74 -0
- package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
- package/lib/commonjs/hooks/useEventCallback.js +17 -0
- package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
- package/lib/commonjs/index.js +234 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native.js +132 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/runUpdateProcess.js +69 -0
- package/lib/commonjs/runUpdateProcess.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
- package/lib/commonjs/store.js +48 -0
- package/lib/commonjs/store.js.map +1 -0
- package/lib/commonjs/wrap.js +98 -0
- package/lib/commonjs/wrap.js.map +1 -0
- package/lib/module/checkForUpdate.js +64 -0
- package/lib/module/checkForUpdate.js.map +1 -0
- package/lib/module/error.js +9 -0
- package/lib/module/error.js.map +1 -0
- package/lib/module/fetchUpdateInfo.js +69 -0
- package/lib/module/fetchUpdateInfo.js.map +1 -0
- package/lib/module/hooks/useEventCallback.js +13 -0
- package/lib/module/hooks/useEventCallback.js.map +1 -0
- package/lib/module/index.js +211 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +119 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/runUpdateProcess.js +64 -0
- package/lib/module/runUpdateProcess.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js +5 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -0
- package/lib/module/store.js +42 -0
- package/lib/module/store.js.map +1 -0
- package/lib/module/wrap.js +94 -0
- package/lib/module/wrap.js.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
- package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +64 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
- package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +22 -0
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -0
- package/lib/typescript/module/error.d.ts.map +1 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +202 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +64 -0
- package/lib/typescript/module/native.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
- package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
- package/lib/typescript/module/store.d.ts +11 -0
- package/lib/typescript/module/store.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts +51 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -0
- package/package.json +59 -30
- package/src/checkForUpdate.ts +59 -9
- package/src/fetchUpdateInfo.ts +40 -12
- package/src/index.ts +37 -11
- package/src/native.ts +87 -41
- package/src/runUpdateProcess.ts +2 -2
- package/src/specs/NativeHotUpdater.ts +8 -10
- package/src/wrap.tsx +9 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
- package/dist/checkForUpdate.d.ts +0 -12
- package/dist/fetchUpdateInfo.d.ts +0 -3
- package/dist/index.js +0 -341
- package/dist/index.mjs +0 -301
- package/dist/native.d.ts +0 -41
- package/ios/HotUpdater/HotUpdater.h +0 -15
- package/ios/HotUpdater/HotUpdater.mm +0 -468
- package/ios/HotUpdater/HotUpdater.modulemap +0 -6
- package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
- package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
- package/react-native.config.js +0 -12
- package/src/global.d.ts +0 -3
- /package/android/{generated → app/build/generated/source/codegen}/jni/CMakeLists.txt +0 -0
- /package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec.h +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#ifndef HotUpdater_Bridging_Header_h
|
|
2
|
+
#define HotUpdater_Bridging_Header_h
|
|
3
|
+
|
|
4
|
+
#import "React/RCTBridgeModule.h"
|
|
5
|
+
#import "React/RCTEventEmitter.h"
|
|
6
|
+
#import "React/RCTUtils.h" // Needed for RCTPromiseResolveBlock/RejectBlock in Swift
|
|
7
|
+
#import <SSZipArchive/SSZipArchive.h>
|
|
8
|
+
#endif /* HotUpdater_Bridging_Header_h */
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#import "HotUpdater.h"
|
|
2
|
+
#import <React/RCTReloadCommand.h>
|
|
3
|
+
#import <React/RCTLog.h>
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#if __has_include("HotUpdater/HotUpdater-Swift.h")
|
|
7
|
+
#import "HotUpdater/HotUpdater-Swift.h"
|
|
8
|
+
#else
|
|
9
|
+
#import "HotUpdater-Swift.h"
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Define Notification names used for observing Swift Core
|
|
14
|
+
NSNotificationName const HotUpdaterDownloadProgressUpdateNotification = @"HotUpdaterDownloadProgressUpdate";
|
|
15
|
+
NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterDownloadDidFinish";
|
|
16
|
+
|
|
17
|
+
// Create static HotUpdaterImpl instance
|
|
18
|
+
static HotUpdaterImpl *_hotUpdaterImpl = [HotUpdaterFactory.shared create];
|
|
19
|
+
|
|
20
|
+
@implementation HotUpdater {
|
|
21
|
+
bool hasListeners;
|
|
22
|
+
// Keep track of tasks ONLY for removing observers when this ObjC instance is invalidated
|
|
23
|
+
NSMutableSet<NSURLSessionTask *> *observedTasks; // Changed to NSURLSessionTask for broader compatibility if needed
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
27
|
+
return YES;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (instancetype)init {
|
|
31
|
+
self = [super init];
|
|
32
|
+
if (self) {
|
|
33
|
+
observedTasks = [NSMutableSet set];
|
|
34
|
+
|
|
35
|
+
// Start observing notifications needed for cleanup/events
|
|
36
|
+
// Using self as observer
|
|
37
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
38
|
+
selector:@selector(handleDownloadProgress:)
|
|
39
|
+
name:HotUpdaterDownloadProgressUpdateNotification
|
|
40
|
+
object:nil]; // Observe all tasks from Impl
|
|
41
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
42
|
+
selector:@selector(handleDownloadCompletion:)
|
|
43
|
+
name:HotUpdaterDownloadDidFinishNotification
|
|
44
|
+
object:nil]; // Observe all tasks from Impl
|
|
45
|
+
|
|
46
|
+
_lastUpdateTime = 0;
|
|
47
|
+
}
|
|
48
|
+
return self;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Clean up observers when module is invalidated or deallocated
|
|
52
|
+
- (void)invalidate {
|
|
53
|
+
RCTLogInfo(@"[HotUpdater.mm] invalidate called, removing observers.");
|
|
54
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
55
|
+
// Swift side should handle KVO observer removal for its tasks
|
|
56
|
+
[super invalidate];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
- (void)dealloc {
|
|
60
|
+
RCTLogInfo(@"[HotUpdater.mm] dealloc called, removing observers.");
|
|
61
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
RCT_EXPORT_MODULE();
|
|
66
|
+
|
|
67
|
+
#pragma mark - React Native Constants (Keep getMinBundleId, delegate others)
|
|
68
|
+
|
|
69
|
+
// Keep local implementation if complex or uses macros
|
|
70
|
+
- (NSString *)getMinBundleId {
|
|
71
|
+
static NSString *uuid = nil;
|
|
72
|
+
static dispatch_once_t onceToken;
|
|
73
|
+
dispatch_once(&onceToken, ^{
|
|
74
|
+
#if DEBUG
|
|
75
|
+
uuid = @"00000000-0000-0000-0000-000000000000";
|
|
76
|
+
#else
|
|
77
|
+
NSString *compileDateStr = [NSString stringWithFormat:@"%s %s", __DATE__, __TIME__];
|
|
78
|
+
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
79
|
+
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
80
|
+
[formatter setDateFormat:@"MMM d yyyy HH:mm:ss"]; // Correct format for __DATE__ __TIME__
|
|
81
|
+
NSDate *buildDate = [formatter dateFromString:compileDateStr];
|
|
82
|
+
if (!buildDate) {
|
|
83
|
+
RCTLogWarn(@"[HotUpdater.mm] Could not parse build date: %@", compileDateStr);
|
|
84
|
+
uuid = @"00000000-0000-0000-0000-000000000000";
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
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]];
|
|
101
|
+
#endif
|
|
102
|
+
});
|
|
103
|
+
return uuid;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
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
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
- (NSDictionary *)getConstants {
|
|
116
|
+
return [self constantsToExport];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// Get bundleURL using static instance
|
|
121
|
+
+ (NSURL *)bundleURL {
|
|
122
|
+
return [_hotUpdaterImpl bundleURL];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
#pragma mark - Progress Updates & Event Emitting (Keep in ObjC Wrapper)
|
|
127
|
+
|
|
128
|
+
- (void)handleDownloadProgress:(NSNotification *)notification {
|
|
129
|
+
if (!hasListeners) return;
|
|
130
|
+
|
|
131
|
+
NSDictionary *userInfo = notification.userInfo;
|
|
132
|
+
NSNumber *progressNum = userInfo[@"progress"];
|
|
133
|
+
|
|
134
|
+
if (progressNum) {
|
|
135
|
+
double progress = [progressNum doubleValue];
|
|
136
|
+
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970] * 1000;
|
|
137
|
+
// Throttle events
|
|
138
|
+
if ((currentTime - self.lastUpdateTime) >= 100 || progress >= 1.0) {
|
|
139
|
+
self.lastUpdateTime = currentTime;
|
|
140
|
+
// RCTLogInfo(@"[HotUpdater.mm] Sending progress event: %.2f", progress); // Reduce log noise
|
|
141
|
+
[self sendEventWithName:@"onProgress" body:@{@"progress": @(progress)}];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)handleDownloadCompletion:(NSNotification *)notification {
|
|
147
|
+
NSURLSessionTask *task = notification.object; // Task that finished
|
|
148
|
+
RCTLogInfo(@"[HotUpdater.mm] Received download completion notification for task: %@", task.originalRequest.URL);
|
|
149
|
+
// Swift side handles KVO observer removal internally now when task finishes.
|
|
150
|
+
// No specific cleanup needed here based on this notification anymore.
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
#pragma mark - React Native Events (Keep as is)
|
|
155
|
+
|
|
156
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
157
|
+
return @[@"onProgress"];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
- (void)startObserving {
|
|
161
|
+
hasListeners = YES;
|
|
162
|
+
RCTLogInfo(@"[HotUpdater.mm] Start observing JS events.");
|
|
163
|
+
// Observers are added in init now
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
- (void)stopObserving {
|
|
167
|
+
hasListeners = NO;
|
|
168
|
+
RCTLogInfo(@"[HotUpdater.mm] Stop observing JS events.");
|
|
169
|
+
// Observers are removed in invalidate/dealloc
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
- (void)sendEventWithName:(NSString * _Nonnull)name body:(id)body { // Changed body type to id
|
|
173
|
+
if (hasListeners) {
|
|
174
|
+
[super sendEventWithName:name body:body];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
#pragma mark - React Native Exports (Slimmed Down)
|
|
180
|
+
|
|
181
|
+
// Keep reload logic here as it interacts with RN Bridge
|
|
182
|
+
RCT_EXPORT_METHOD(reload) {
|
|
183
|
+
RCTLogInfo(@"[HotUpdater.mm] HotUpdater requested a reload");
|
|
184
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
185
|
+
// Get bundleURL using static instance
|
|
186
|
+
NSURL *bundleURL = [_hotUpdaterImpl bundleURL];
|
|
187
|
+
RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
|
|
188
|
+
if (bundleURL && super.bridge) {
|
|
189
|
+
@try {
|
|
190
|
+
// This method of setting bundleURL might be outdated depending on RN version.
|
|
191
|
+
// Consider alternatives if this doesn't work reliably.
|
|
192
|
+
[super.bridge setValue:bundleURL forKey:@"bundleURL"];
|
|
193
|
+
} @catch (NSException *exception) {
|
|
194
|
+
RCTLogError(@"[HotUpdater.mm] Failed to set bundleURL on bridge: %@", exception);
|
|
195
|
+
}
|
|
196
|
+
} else if (!super.bridge) {
|
|
197
|
+
RCTLogWarn(@"[HotUpdater.mm] Bridge is nil, cannot set bundleURL for reload.");
|
|
198
|
+
}
|
|
199
|
+
RCTTriggerReloadCommandListeners(@"HotUpdater requested a reload");
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
204
|
+
|
|
205
|
+
RCT_EXPORT_METHOD(updateBundle:(JS::NativeHotUpdater::UpdateBundleParams &)params
|
|
206
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
207
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
208
|
+
NSLog(@"[HotUpdater.mm] updateBundle called.");
|
|
209
|
+
NSMutableDictionary *paramDict = [NSMutableDictionary dictionary];
|
|
210
|
+
if (params.bundleId()) {
|
|
211
|
+
paramDict[@"bundleId"] = params.bundleId();
|
|
212
|
+
}
|
|
213
|
+
if (params.fileUrl()) {
|
|
214
|
+
paramDict[@"fileUrl"] = params.fileUrl();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[_hotUpdaterImpl updateBundle:paramDict resolver:resolve rejecter:reject];
|
|
218
|
+
}
|
|
219
|
+
#else
|
|
220
|
+
RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
|
|
221
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
222
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
223
|
+
NSLog(@"[HotUpdater.mm] updateBundle called. params: %@", params);
|
|
224
|
+
[_hotUpdaterImpl updateBundle:params resolver:resolve rejecter:reject];
|
|
225
|
+
}
|
|
226
|
+
#endif
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
#pragma mark - Turbo Module Support (Keep as is)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
235
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
236
|
+
return std::make_shared<facebook::react::NativeHotUpdaterSpecJSI>(params);
|
|
237
|
+
}
|
|
238
|
+
#endif
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@objcMembers
|
|
4
|
+
public class HotUpdaterFactory: NSObject {
|
|
5
|
+
public static let shared = HotUpdaterFactory()
|
|
6
|
+
|
|
7
|
+
private override init() {}
|
|
8
|
+
|
|
9
|
+
public func create() -> HotUpdaterImpl {
|
|
10
|
+
let fileSystem = FileManagerService()
|
|
11
|
+
let preferences = VersionedPreferencesService()
|
|
12
|
+
let downloadService = URLSessionDownloadService()
|
|
13
|
+
let unzipService = SSZipArchiveUnzipService()
|
|
14
|
+
|
|
15
|
+
let bundleStorage = BundleFileStorageService(
|
|
16
|
+
fileSystem: fileSystem,
|
|
17
|
+
downloadService: downloadService,
|
|
18
|
+
unzipService: unzipService,
|
|
19
|
+
preferences: preferences
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return HotUpdaterImpl(bundleStorage: bundleStorage, preferences: preferences)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objcMembers public class HotUpdaterImpl: NSObject {
|
|
5
|
+
private let bundleStorage: BundleStorageService
|
|
6
|
+
private let preferences: PreferencesService
|
|
7
|
+
|
|
8
|
+
// MARK: - Initialization
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convenience initializer that creates and configures all dependencies.
|
|
12
|
+
*/
|
|
13
|
+
public convenience override init() {
|
|
14
|
+
let fileSystem = FileManagerService()
|
|
15
|
+
let preferences = VersionedPreferencesService()
|
|
16
|
+
let downloadService = URLSessionDownloadService()
|
|
17
|
+
let unzipService = SSZipArchiveUnzipService()
|
|
18
|
+
|
|
19
|
+
let bundleStorage = BundleFileStorageService(
|
|
20
|
+
fileSystem: fileSystem,
|
|
21
|
+
downloadService: downloadService,
|
|
22
|
+
unzipService: unzipService,
|
|
23
|
+
preferences: preferences
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
self.init(bundleStorage: bundleStorage, preferences: preferences)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Primary initializer with dependency injection.
|
|
31
|
+
* @param bundleStorage Service for bundle storage operations
|
|
32
|
+
* @param preferences Service for preference storage
|
|
33
|
+
*/
|
|
34
|
+
internal init(bundleStorage: BundleStorageService, preferences: PreferencesService) {
|
|
35
|
+
self.bundleStorage = bundleStorage
|
|
36
|
+
self.preferences = preferences
|
|
37
|
+
super.init()
|
|
38
|
+
|
|
39
|
+
// Configure preferences with app version
|
|
40
|
+
if let appVersion = HotUpdaterImpl.appVersion {
|
|
41
|
+
(preferences as? VersionedPreferencesService)?.configure(appVersion: appVersion)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - Static Properties
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns the app version from main bundle info.
|
|
49
|
+
*/
|
|
50
|
+
public static var appVersion: String? {
|
|
51
|
+
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// MARK: - Channel Management
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Gets the current update channel.
|
|
58
|
+
* @return The channel name or nil if not set
|
|
59
|
+
*/
|
|
60
|
+
private static let DEFAULT_CHANNEL = "production"
|
|
61
|
+
|
|
62
|
+
public func getChannel() -> String {
|
|
63
|
+
return Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_CHANNEL") as? String ?? Self.DEFAULT_CHANNEL
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// MARK: - Bundle URL Management
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the URL to the bundle file.
|
|
71
|
+
* @return URL to the bundle or nil
|
|
72
|
+
*/
|
|
73
|
+
public func bundleURL() -> URL? {
|
|
74
|
+
return bundleStorage.getBundleURL()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// MARK: - Bundle Update
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Updates the bundle from JavaScript bridge.
|
|
81
|
+
* This method acts as the primary error boundary for all bundle operations.
|
|
82
|
+
* @param params Dictionary with bundleId and fileUrl parameters
|
|
83
|
+
* @param resolve Promise resolve callback
|
|
84
|
+
* @param reject Promise reject callback
|
|
85
|
+
*/
|
|
86
|
+
public func updateBundle(_ params: NSDictionary?,
|
|
87
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
88
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
89
|
+
|
|
90
|
+
do {
|
|
91
|
+
// Validate parameters (this runs on calling thread - typically JS thread)
|
|
92
|
+
guard let data = params else {
|
|
93
|
+
throw NSError(domain: "HotUpdaterError", code: 101,
|
|
94
|
+
userInfo: [NSLocalizedDescriptionKey: "Missing params dictionary"])
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
guard let bundleId = data["bundleId"] as? String, !bundleId.isEmpty else {
|
|
98
|
+
throw NSError(domain: "HotUpdaterError", code: 102,
|
|
99
|
+
userInfo: [NSLocalizedDescriptionKey: "Missing or empty 'bundleId'"])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let fileUrlString = data["fileUrl"] as? String ?? ""
|
|
103
|
+
|
|
104
|
+
var fileUrl: URL? = nil
|
|
105
|
+
if !fileUrlString.isEmpty {
|
|
106
|
+
guard let url = URL(string: fileUrlString) else {
|
|
107
|
+
throw NSError(domain: "HotUpdaterError", code: 103,
|
|
108
|
+
userInfo: [NSLocalizedDescriptionKey: "Invalid 'fileUrl' provided: \(fileUrlString)"])
|
|
109
|
+
}
|
|
110
|
+
fileUrl = url
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), fileUrl: \(fileUrl?.absoluteString ?? "nil")")
|
|
114
|
+
|
|
115
|
+
// Heavy work is delegated to bundle storage service with safe error handling
|
|
116
|
+
bundleStorage.updateBundle(bundleId: bundleId, fileUrl: fileUrl) { [weak self] result in
|
|
117
|
+
guard self != nil else {
|
|
118
|
+
let error = NSError(domain: "HotUpdaterError", code: 998,
|
|
119
|
+
userInfo: [NSLocalizedDescriptionKey: "Self deallocated during update"])
|
|
120
|
+
DispatchQueue.main.async {
|
|
121
|
+
reject("UPDATE_ERROR", error.localizedDescription, error)
|
|
122
|
+
}
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
// Return results on main thread for React Native bridge
|
|
126
|
+
DispatchQueue.main.async {
|
|
127
|
+
switch result {
|
|
128
|
+
case .success:
|
|
129
|
+
NSLog("[HotUpdaterImpl] Update successful for \(bundleId). Resolving promise.")
|
|
130
|
+
resolve(true)
|
|
131
|
+
case .failure(let error):
|
|
132
|
+
NSLog("[HotUpdaterImpl] Update failed for \(bundleId): \(error.localizedDescription). Rejecting promise.")
|
|
133
|
+
reject("UPDATE_ERROR", error.localizedDescription, error)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch let error {
|
|
138
|
+
// Main error boundary - catch and convert all errors to JS rejection
|
|
139
|
+
NSLog("[HotUpdaterImpl] Error in updateBundleFromJS: \(error.localizedDescription)")
|
|
140
|
+
reject("UPDATE_ERROR", error.localizedDescription, error)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SSZipArchive
|
|
3
|
+
|
|
4
|
+
protocol UnzipService {
|
|
5
|
+
/**
|
|
6
|
+
* Unzips a file to a destination directory.
|
|
7
|
+
* @param file Path to the zip file
|
|
8
|
+
* @param destination Directory to extract to
|
|
9
|
+
* @throws Error if unzipping fails
|
|
10
|
+
*/
|
|
11
|
+
func unzip(file: String, to destination: String) throws
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class SSZipArchiveUnzipService: UnzipService {
|
|
15
|
+
func unzip(file: String, to destination: String) throws {
|
|
16
|
+
var error: Error?
|
|
17
|
+
|
|
18
|
+
do {
|
|
19
|
+
try SSZipArchive.unzipFile(atPath: file, toDestination: destination, overwrite: true, password: nil)
|
|
20
|
+
} catch let caughtError {
|
|
21
|
+
error = caughtError
|
|
22
|
+
throw error!
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
protocol DownloadService {
|
|
4
|
+
/**
|
|
5
|
+
* Downloads a file from a URL.
|
|
6
|
+
* @param url The URL to download from
|
|
7
|
+
* @param destination The local path to save to
|
|
8
|
+
* @param progressHandler Callback for download progress updates
|
|
9
|
+
* @param completion Callback with result of the download
|
|
10
|
+
* @return The download task (optional)
|
|
11
|
+
*/
|
|
12
|
+
func downloadFile(from url: URL, to destination: String, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask?
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class URLSessionDownloadService: NSObject, DownloadService {
|
|
17
|
+
private var session: URLSession!
|
|
18
|
+
private var progressHandlers: [URLSessionTask: (Double) -> Void] = [:]
|
|
19
|
+
private var completionHandlers: [URLSessionTask: (Result<URL, Error>) -> Void] = [:]
|
|
20
|
+
private var destinations: [URLSessionTask: String] = [:]
|
|
21
|
+
|
|
22
|
+
override init() {
|
|
23
|
+
super.init()
|
|
24
|
+
let configuration = URLSessionConfiguration.default
|
|
25
|
+
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func downloadFile(from url: URL, to destination: String, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask? {
|
|
29
|
+
let task = session.downloadTask(with: url)
|
|
30
|
+
progressHandlers[task] = progressHandler
|
|
31
|
+
completionHandlers[task] = completion
|
|
32
|
+
destinations[task] = destination
|
|
33
|
+
task.resume()
|
|
34
|
+
return task
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
extension URLSessionDownloadService: URLSessionDownloadDelegate {
|
|
39
|
+
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
|
40
|
+
let completion = completionHandlers[downloadTask]
|
|
41
|
+
let destination = destinations[downloadTask]
|
|
42
|
+
|
|
43
|
+
defer {
|
|
44
|
+
progressHandlers.removeValue(forKey: downloadTask)
|
|
45
|
+
completionHandlers.removeValue(forKey: downloadTask)
|
|
46
|
+
destinations.removeValue(forKey: downloadTask)
|
|
47
|
+
|
|
48
|
+
// 다운로드 완료 알림
|
|
49
|
+
NotificationCenter.default.post(name: .downloadDidFinish, object: downloadTask)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
guard let destination = destination else {
|
|
53
|
+
completion?(.failure(NSError(domain: "HotUpdaterError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Destination path not found"])))
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
do {
|
|
58
|
+
let destinationURL = URL(fileURLWithPath: destination)
|
|
59
|
+
try FileManager.default.copyItem(at: location, to: destinationURL)
|
|
60
|
+
completion?(.success(destinationURL))
|
|
61
|
+
} catch {
|
|
62
|
+
NSLog("[DownloadService] Failed to copy downloaded file: \(error.localizedDescription)")
|
|
63
|
+
completion?(.failure(error))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
68
|
+
let completion = completionHandlers[task]
|
|
69
|
+
defer {
|
|
70
|
+
progressHandlers.removeValue(forKey: task)
|
|
71
|
+
completionHandlers.removeValue(forKey: task)
|
|
72
|
+
destinations.removeValue(forKey: task)
|
|
73
|
+
|
|
74
|
+
NotificationCenter.default.post(name: .downloadDidFinish, object: task)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if let error = error {
|
|
78
|
+
completion?(.failure(error))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
|
83
|
+
let progressHandler = progressHandlers[downloadTask]
|
|
84
|
+
|
|
85
|
+
if totalBytesExpectedToWrite > 0 {
|
|
86
|
+
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
|
|
87
|
+
progressHandler?(progress)
|
|
88
|
+
|
|
89
|
+
let progressInfo: [String: Any] = [
|
|
90
|
+
"progress": progress,
|
|
91
|
+
"totalBytesReceived": totalBytesWritten,
|
|
92
|
+
"totalBytesExpected": totalBytesExpectedToWrite
|
|
93
|
+
]
|
|
94
|
+
NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: progressInfo)
|
|
95
|
+
} else {
|
|
96
|
+
progressHandler?(0)
|
|
97
|
+
|
|
98
|
+
NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: ["progress": 0.0, "totalBytesReceived": 0, "totalBytesExpected": 0])
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum PreferencesError: Error {
|
|
4
|
+
case configurationError
|
|
5
|
+
case setItemError(String)
|
|
6
|
+
case getItemError(String)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
protocol PreferencesService {
|
|
10
|
+
func setItem(_ value: String?, forKey key: String) throws
|
|
11
|
+
func getItem(forKey key: String) throws -> String?
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class VersionedPreferencesService: PreferencesService {
|
|
15
|
+
private let userDefaults: UserDefaults
|
|
16
|
+
private var keyPrefix: String = ""
|
|
17
|
+
|
|
18
|
+
init(userDefaults: UserDefaults = .standard) {
|
|
19
|
+
self.userDefaults = userDefaults
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configures the service with app version for key prefixing.
|
|
24
|
+
* @param appVersion The app version to use for key prefixing
|
|
25
|
+
*/
|
|
26
|
+
func configure(appVersion: String?) {
|
|
27
|
+
self.keyPrefix = "hotupdater_\(appVersion ?? "unknown")_"
|
|
28
|
+
NSLog("[PreferencesService] Configured with appVersion: \(appVersion ?? "nil"). Key prefix: \(self.keyPrefix)")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a prefixed key for UserDefaults storage.
|
|
33
|
+
* @param key The base key to prefix
|
|
34
|
+
* @return The prefixed key
|
|
35
|
+
* @throws PreferencesError if configuration is missing
|
|
36
|
+
*/
|
|
37
|
+
private func prefixedKey(forKey key: String) throws -> String {
|
|
38
|
+
guard !keyPrefix.isEmpty else {
|
|
39
|
+
NSLog("[PreferencesService] Warning: PreferencesService used before configure(appVersion:) was called. Key prefix is empty.")
|
|
40
|
+
throw PreferencesError.configurationError
|
|
41
|
+
}
|
|
42
|
+
return "\(keyPrefix)\(key)"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sets a value in preferences.
|
|
47
|
+
* @param value The value to store (or nil to remove)
|
|
48
|
+
* @param key The key to store under
|
|
49
|
+
* @throws PreferencesError if key prefixing fails
|
|
50
|
+
*/
|
|
51
|
+
func setItem(_ value: String?, forKey key: String) throws {
|
|
52
|
+
do {
|
|
53
|
+
let fullKey = try prefixedKey(forKey: key)
|
|
54
|
+
if let valueToSet = value {
|
|
55
|
+
userDefaults.set(valueToSet, forKey: fullKey)
|
|
56
|
+
NSLog("[PreferencesService] Set '\(fullKey)' = '\(valueToSet)'")
|
|
57
|
+
} else {
|
|
58
|
+
userDefaults.removeObject(forKey: fullKey)
|
|
59
|
+
NSLog("[PreferencesService] Removed '\(fullKey)'")
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
NSLog("[PreferencesService] Error setting key '\(key)': \(error)")
|
|
63
|
+
throw PreferencesError.setItemError(key)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets a value from preferences.
|
|
69
|
+
* @param key The key to retrieve
|
|
70
|
+
* @return The stored value or nil if not found
|
|
71
|
+
* @throws PreferencesError if key prefixing fails
|
|
72
|
+
*/
|
|
73
|
+
func getItem(forKey key: String) throws -> String? {
|
|
74
|
+
do {
|
|
75
|
+
let fullKey = try prefixedKey(forKey: key)
|
|
76
|
+
return userDefaults.string(forKey: fullKey)
|
|
77
|
+
} catch {
|
|
78
|
+
NSLog("[PreferencesService] Error getting key '\(key)': \(error)")
|
|
79
|
+
throw PreferencesError.getItemError(key)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"originHash" : "11a19903c341bf413adfdb15a6fb5dff9a819b0557a6cfe9e5fe00f32a0ff74e",
|
|
3
|
+
"pins" : [
|
|
4
|
+
{
|
|
5
|
+
"identity" : "ziparchive",
|
|
6
|
+
"kind" : "remoteSourceControl",
|
|
7
|
+
"location" : "https://github.com/ZipArchive/ZipArchive.git",
|
|
8
|
+
"state" : {
|
|
9
|
+
"revision" : "df35718ea19a94e015b91dc4881dee028ce4cdba",
|
|
10
|
+
"version" : "2.6.0"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"version" : 3
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#import <React/RCTEventEmitter.h>
|
|
2
|
+
#import <React/RCTBundleURLProvider.h>
|
|
3
|
+
|
|
4
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
5
|
+
#import <HotUpdaterSpec/HotUpdaterSpec.h>
|
|
6
|
+
@interface HotUpdater : RCTEventEmitter <NativeHotUpdaterSpec>
|
|
7
|
+
#else
|
|
8
|
+
#import <React/RCTBridgeModule.h>
|
|
9
|
+
@interface HotUpdater : RCTEventEmitter <RCTBridgeModule>
|
|
10
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the currently active bundle URL.
|
|
14
|
+
* Callable from Objective-C (e.g., AppDelegate).
|
|
15
|
+
* This is implemented in HotUpdater.mm and calls the Swift static method.
|
|
16
|
+
*/
|
|
17
|
+
+ (NSURL *)bundleURL;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 다운로드 진행 상황 업데이트 시간을 추적하는 속성
|
|
21
|
+
*/
|
|
22
|
+
@property (nonatomic, assign) NSTimeInterval lastUpdateTime;
|
|
23
|
+
|
|
24
|
+
// No need to declare the exported methods (reload, etc.) here
|
|
25
|
+
// as RCT_EXPORT_METHOD handles their exposure to JavaScript.
|
|
26
|
+
// We also don't need to declare supportedEvents or requiresMainQueueSetup here
|
|
27
|
+
// as they are implemented in the .mm file (calling Swift).
|
|
28
|
+
|
|
29
|
+
@end
|