@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.
@@ -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
+ }
@@ -9,6 +9,7 @@
9
9
  @interface HotUpdater : RCTEventEmitter <RCTBridgeModule>
10
10
  #endif // RCT_NEW_ARCH_ENABLED
11
11
 
12
+ @property (nonatomic, assign) NSTimeInterval lastUpdateTime;
12
13
  + (NSURL *)bundleURL;
13
14
 
14
15
  @end
@@ -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
- - (BOOL)updateBundle:(NSString *)bundleId zipUrl:(NSURL *)zipUrl {
84
+ - (void)updateBundle:(NSString *)bundleId zipUrl:(NSURL *)zipUrl completion:(void (^)(BOOL success))completion {
75
85
  if (!zipUrl) {
76
- [self setBundleURL:nil];
77
- return YES;
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
- success = NO;
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
- success = NO;
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
- success = NO;
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
- success = NO;
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
- success = NO;
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
- [self setBundleURL:bundlePath];
152
- success = YES;
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
- success = NO;
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
- forKeyPath:@"countOfBytesReceived"
164
- options:NSKeyValueObservingOptionNew
165
- context:nil];
169
+ forKeyPath:@"countOfBytesReceived"
170
+ options:NSKeyValueObservingOptionNew
171
+ context:nil];
166
172
  [downloadTask addObserver:self
167
- forKeyPath:@"countOfBytesExpectedToReceive"
168
- options:NSKeyValueObservingOptionNew
169
- context:nil];
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
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
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
- // Send progress to React Native
190
- [self sendEventWithName:@"onProgress" body:@{@"progress": @(progress)}];
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
- BOOL result = [self updateBundle:bundleId zipUrl:zipUrl];
239
- resolve(@[@(result)]);
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.7.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.7.0",
82
- "@hot-updater/core": "0.7.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",