@hot-updater/react-native 0.7.0 → 0.9.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
CHANGED
|
@@ -65,7 +65,7 @@ android {
|
|
|
65
65
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
66
66
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
67
67
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
68
|
-
|
|
68
|
+
consumerProguardFiles 'proguard-rules.pro'
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
buildFeatures {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Old Architecture
|
|
2
|
+
# Invoked via reflection, when setting js bundle.
|
|
3
|
+
-keepclassmembers class com.facebook.react.ReactInstanceManager {
|
|
4
|
+
private final ** mBundleLoader;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
# New Architecture
|
|
8
|
+
# Keep fields accessed via reflection in ReactHost
|
|
9
|
+
-keepclassmembers class com.facebook.react.runtime.ReactHostImpl {
|
|
10
|
+
private final ** mReactHostDelegate;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
-keepclassmembers class * implements com.facebook.react.runtime.ReactHostDelegate {
|
|
14
|
+
** jsBundleLoader;
|
|
15
|
+
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
#import "HotUpdater.h"
|
|
2
2
|
#import <React/RCTReloadCommand.h>
|
|
3
3
|
#import <SSZipArchive/SSZipArchive.h>
|
|
4
|
+
#import <Foundation/NSURLSession.h>
|
|
4
5
|
|
|
5
6
|
@implementation HotUpdater {
|
|
6
7
|
bool hasListeners;
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
|
|
11
|
+
- (instancetype)init {
|
|
12
|
+
self = [super init];
|
|
13
|
+
if (self) {
|
|
14
|
+
_lastUpdateTime = 0;
|
|
15
|
+
}
|
|
16
|
+
return self;
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
RCT_EXPORT_MODULE();
|
|
10
20
|
|
|
11
21
|
#pragma mark - Bundle URL Management
|
|
@@ -71,71 +81,66 @@ RCT_EXPORT_MODULE();
|
|
|
71
81
|
return success;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
- (
|
|
84
|
+
- (void)updateBundle:(NSString *)bundleId zipUrl:(NSURL *)zipUrl completion:(void (^)(BOOL success))completion {
|
|
75
85
|
if (!zipUrl) {
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
87
|
+
[self setBundleURL:nil];
|
|
88
|
+
if (completion) completion(YES);
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
78
91
|
}
|
|
79
|
-
|
|
92
|
+
|
|
80
93
|
NSString *basePath = [self stripPrefixFromPath:bundleId path:[zipUrl path]];
|
|
81
94
|
NSString *path = [self convertFileSystemPathFromBasePath:basePath];
|
|
82
|
-
|
|
95
|
+
|
|
83
96
|
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
84
97
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
|
|
85
|
-
|
|
86
|
-
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
|
87
|
-
__block BOOL success = NO;
|
|
88
|
-
|
|
98
|
+
|
|
89
99
|
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:zipUrl
|
|
90
100
|
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
|
|
91
101
|
if (error) {
|
|
92
102
|
NSLog(@"Failed to download data from URL: %@, error: %@", zipUrl, error);
|
|
93
|
-
|
|
94
|
-
dispatch_semaphore_signal(semaphore);
|
|
103
|
+
if (completion) completion(NO);
|
|
95
104
|
return;
|
|
96
105
|
}
|
|
97
|
-
|
|
106
|
+
|
|
98
107
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
99
108
|
NSError *folderError;
|
|
100
|
-
|
|
109
|
+
|
|
101
110
|
// Ensure directory exists
|
|
102
111
|
if (![fileManager createDirectoryAtPath:[path stringByDeletingLastPathComponent]
|
|
103
112
|
withIntermediateDirectories:YES
|
|
104
113
|
attributes:nil
|
|
105
114
|
error:&folderError]) {
|
|
106
115
|
NSLog(@"Failed to create folder: %@", folderError);
|
|
107
|
-
|
|
108
|
-
dispatch_semaphore_signal(semaphore);
|
|
116
|
+
if (completion) completion(NO);
|
|
109
117
|
return;
|
|
110
118
|
}
|
|
111
|
-
|
|
119
|
+
|
|
112
120
|
// Check if file already exists and remove it
|
|
113
121
|
if ([fileManager fileExistsAtPath:path]) {
|
|
114
122
|
NSError *removeError;
|
|
115
123
|
if (![fileManager removeItemAtPath:path error:&removeError]) {
|
|
116
124
|
NSLog(@"Failed to remove existing file: %@", removeError);
|
|
117
|
-
|
|
118
|
-
dispatch_semaphore_signal(semaphore);
|
|
125
|
+
if (completion) completion(NO);
|
|
119
126
|
return;
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
|
-
|
|
129
|
+
|
|
123
130
|
NSError *moveError;
|
|
124
131
|
if (![fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:&moveError]) {
|
|
125
132
|
NSLog(@"Failed to save data: %@", moveError);
|
|
126
|
-
|
|
127
|
-
dispatch_semaphore_signal(semaphore);
|
|
133
|
+
if (completion) completion(NO);
|
|
128
134
|
return;
|
|
129
135
|
}
|
|
130
|
-
|
|
136
|
+
|
|
131
137
|
NSString *extractedPath = [path stringByDeletingLastPathComponent];
|
|
132
138
|
if (![self extractZipFileAtPath:path toDestination:extractedPath]) {
|
|
133
139
|
NSLog(@"Failed to extract zip file.");
|
|
134
|
-
|
|
135
|
-
dispatch_semaphore_signal(semaphore);
|
|
140
|
+
if (completion) completion(NO);
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
|
-
|
|
143
|
+
|
|
139
144
|
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:extractedPath];
|
|
140
145
|
NSString *filename = nil;
|
|
141
146
|
for (NSString *file in enumerator) {
|
|
@@ -144,38 +149,58 @@ RCT_EXPORT_MODULE();
|
|
|
144
149
|
break;
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
|
-
|
|
152
|
+
|
|
148
153
|
if (filename) {
|
|
149
154
|
NSString *bundlePath = [extractedPath stringByAppendingPathComponent:filename];
|
|
150
155
|
NSLog(@"Setting bundle URL: %@", bundlePath);
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
157
|
+
[self setBundleURL:bundlePath];
|
|
158
|
+
if (completion) completion(YES);
|
|
159
|
+
});
|
|
153
160
|
} else {
|
|
154
161
|
NSLog(@"index.ios.bundle not found.");
|
|
155
|
-
|
|
162
|
+
if (completion) completion(NO);
|
|
156
163
|
}
|
|
157
|
-
|
|
158
|
-
dispatch_semaphore_signal(semaphore);
|
|
159
164
|
}];
|
|
165
|
+
|
|
160
166
|
|
|
161
167
|
// Add observer for progress updates
|
|
162
168
|
[downloadTask addObserver:self
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
forKeyPath:@"countOfBytesReceived"
|
|
170
|
+
options:NSKeyValueObservingOptionNew
|
|
171
|
+
context:nil];
|
|
166
172
|
[downloadTask addObserver:self
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
173
|
+
forKeyPath:@"countOfBytesExpectedToReceive"
|
|
174
|
+
options:NSKeyValueObservingOptionNew
|
|
175
|
+
context:nil];
|
|
176
|
+
|
|
177
|
+
__block HotUpdater *weakSelf = self;
|
|
178
|
+
[[NSNotificationCenter defaultCenter] addObserverForName:@"NSURLSessionDownloadTaskDidFinishDownloading"
|
|
179
|
+
object:downloadTask
|
|
180
|
+
queue:[NSOperationQueue mainQueue]
|
|
181
|
+
usingBlock:^(NSNotification * _Nonnull note) {
|
|
182
|
+
[weakSelf removeObserversForTask:downloadTask];
|
|
183
|
+
}];
|
|
171
184
|
[downloadTask resume];
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return success;
|
|
185
|
+
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
#pragma mark - Progress Updates
|
|
178
189
|
|
|
190
|
+
|
|
191
|
+
- (void)removeObserversForTask:(NSURLSessionDownloadTask *)task {
|
|
192
|
+
@try {
|
|
193
|
+
if ([task observationInfo]) {
|
|
194
|
+
[task removeObserver:self forKeyPath:@"countOfBytesReceived"];
|
|
195
|
+
[task removeObserver:self forKeyPath:@"countOfBytesExpectedToReceive"];
|
|
196
|
+
NSLog(@"KVO observers removed successfully for task: %@", task);
|
|
197
|
+
}
|
|
198
|
+
} @catch (NSException *exception) {
|
|
199
|
+
NSLog(@"Failed to remove observers: %@", exception);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
179
204
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
180
205
|
ofObject:(id)object
|
|
181
206
|
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
|
|
@@ -186,8 +211,16 @@ RCT_EXPORT_MODULE();
|
|
|
186
211
|
if (task.countOfBytesExpectedToReceive > 0) {
|
|
187
212
|
double progress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
|
|
188
213
|
|
|
189
|
-
//
|
|
190
|
-
[
|
|
214
|
+
// Get current timestamp
|
|
215
|
+
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970] * 1000; // Convert to milliseconds
|
|
216
|
+
|
|
217
|
+
// Send event only if 100ms has passed OR progress is 100%
|
|
218
|
+
if ((currentTime - self.lastUpdateTime) >= 100 || progress >= 1.0) {
|
|
219
|
+
self.lastUpdateTime = currentTime; // Update last event timestamp
|
|
220
|
+
|
|
221
|
+
// Send progress to React Native
|
|
222
|
+
[self sendEventWithName:@"onProgress" body:@{@"progress": @(progress)}];
|
|
223
|
+
}
|
|
191
224
|
}
|
|
192
225
|
}
|
|
193
226
|
}
|
|
@@ -234,9 +267,12 @@ RCT_EXPORT_METHOD(updateBundle:(NSString *)bundleId zipUrl:(NSString *)zipUrlStr
|
|
|
234
267
|
if (![zipUrlString isEqualToString:@""]) {
|
|
235
268
|
zipUrl = [NSURL URLWithString:zipUrlString];
|
|
236
269
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
270
|
+
|
|
271
|
+
[self updateBundle:bundleId zipUrl:zipUrl completion:^(BOOL success) {
|
|
272
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
273
|
+
resolve(@[@(success)]);
|
|
274
|
+
});
|
|
275
|
+
}];
|
|
240
276
|
}
|
|
241
277
|
|
|
242
278
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"react-native-builder-bob": "^0.33.1"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@hot-updater/js": "0.
|
|
82
|
-
"@hot-updater/core": "0.
|
|
81
|
+
"@hot-updater/js": "0.9.0",
|
|
82
|
+
"@hot-updater/core": "0.9.0"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"build": "rslib build",
|