react-native-ota-hot-update 1.1.5 → 1.1.7
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.
|
@@ -15,6 +15,9 @@ import java.io.FileInputStream;
|
|
|
15
15
|
import java.io.FileOutputStream;
|
|
16
16
|
import java.io.InputStream;
|
|
17
17
|
import java.io.OutputStream;
|
|
18
|
+
import java.text.SimpleDateFormat;
|
|
19
|
+
import java.util.Date;
|
|
20
|
+
import java.util.Locale;
|
|
18
21
|
import java.util.zip.ZipEntry;
|
|
19
22
|
import java.util.zip.ZipInputStream;
|
|
20
23
|
import androidx.annotation.NonNull;
|
|
@@ -39,13 +42,14 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
39
42
|
// Finally, delete the empty directory or file
|
|
40
43
|
return directory.delete();
|
|
41
44
|
}
|
|
42
|
-
private boolean deleteOldBundleIfneeded() {
|
|
45
|
+
private boolean deleteOldBundleIfneeded(String pathKey) {
|
|
46
|
+
String pathName = pathKey != null ? pathKey : Common.INSTANCE.getPREVIOUS_PATH();
|
|
43
47
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
44
|
-
String path = sharedPrefs.getString(
|
|
48
|
+
String path = sharedPrefs.getString(pathName);
|
|
45
49
|
File file = new File(path);
|
|
46
50
|
if (file.exists() && file.isFile()) {
|
|
47
51
|
boolean isDeleted = deleteDirectory(file.getParentFile());
|
|
48
|
-
sharedPrefs.
|
|
52
|
+
sharedPrefs.putString(pathName, "");
|
|
49
53
|
return isDeleted;
|
|
50
54
|
} else {
|
|
51
55
|
return false;
|
|
@@ -53,7 +57,8 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
53
57
|
}
|
|
54
58
|
private String unzip(File zipFile, String extension) {
|
|
55
59
|
File destDir = zipFile.getParentFile(); // Directory of the zip file
|
|
56
|
-
|
|
60
|
+
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
|
61
|
+
String topLevelFolder = null;
|
|
57
62
|
String bundleFilePath = null;
|
|
58
63
|
if (!destDir.exists()) {
|
|
59
64
|
destDir.mkdirs();
|
|
@@ -65,6 +70,12 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
65
70
|
ZipEntry zipEntry;
|
|
66
71
|
while ((zipEntry = zis.getNextEntry()) != null) {
|
|
67
72
|
File newFile = new File(destDir, zipEntry.getName());
|
|
73
|
+
if (topLevelFolder == null) {
|
|
74
|
+
String[] parts = zipEntry.getName().split("/");
|
|
75
|
+
if (parts.length > 1) {
|
|
76
|
+
topLevelFolder = parts[0];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
68
79
|
if (zipEntry.isDirectory()) {
|
|
69
80
|
newFile.mkdirs();
|
|
70
81
|
} else {
|
|
@@ -87,6 +98,14 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
87
98
|
} catch (Exception e) {
|
|
88
99
|
return null;
|
|
89
100
|
}
|
|
101
|
+
if (topLevelFolder != null) {
|
|
102
|
+
File extractedFolder = new File(destDir, topLevelFolder);
|
|
103
|
+
File renameFolder = new File(destDir, "output_" + timestamp);
|
|
104
|
+
if (extractedFolder.exists()) {
|
|
105
|
+
extractedFolder.renameTo(renameFolder);
|
|
106
|
+
bundleFilePath = bundleFilePath.replace(extractedFolder.getAbsolutePath(), renameFolder.getAbsolutePath());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
90
109
|
return bundleFilePath;
|
|
91
110
|
}
|
|
92
111
|
|
|
@@ -95,12 +114,16 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
95
114
|
if (path != null) {
|
|
96
115
|
File file = new File(path);
|
|
97
116
|
if (file.exists() && file.isFile()) {
|
|
98
|
-
deleteOldBundleIfneeded();
|
|
117
|
+
deleteOldBundleIfneeded(null);
|
|
99
118
|
String fileUnzip = unzip(file, extension != null ? extension : ".bundle");
|
|
100
119
|
if (fileUnzip != null) {
|
|
101
120
|
Log.d("setupBundlePath: ", fileUnzip);
|
|
102
121
|
file.delete();
|
|
103
122
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
123
|
+
String oldPath = sharedPrefs.getString(Common.INSTANCE.getPATH());
|
|
124
|
+
if (!oldPath.equals("")) {
|
|
125
|
+
sharedPrefs.putString(Common.INSTANCE.getPREVIOUS_PATH(), oldPath);
|
|
126
|
+
}
|
|
104
127
|
sharedPrefs.putString(Common.INSTANCE.getPATH(), fileUnzip);
|
|
105
128
|
PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
|
|
106
129
|
String latestVer = null;
|
|
@@ -126,10 +149,11 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
126
149
|
|
|
127
150
|
@ReactMethod
|
|
128
151
|
public void deleteBundle(Promise promise) {
|
|
129
|
-
boolean isDeleted = deleteOldBundleIfneeded();
|
|
152
|
+
boolean isDeleted = deleteOldBundleIfneeded(null);
|
|
153
|
+
boolean isDeletedOld = deleteOldBundleIfneeded(Common.INSTANCE.getPATH());
|
|
130
154
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
131
155
|
sharedPrefs.putString(Common.INSTANCE.getVERSION(), "0");
|
|
132
|
-
promise.resolve(isDeleted);
|
|
156
|
+
promise.resolve(isDeleted && isDeletedOld);
|
|
133
157
|
}
|
|
134
158
|
@ReactMethod
|
|
135
159
|
public void restart() {
|
|
@@ -147,6 +171,24 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
147
171
|
}
|
|
148
172
|
}
|
|
149
173
|
@ReactMethod
|
|
174
|
+
public void rollbackToPreviousBundle(Promise promise) {
|
|
175
|
+
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
176
|
+
String oldPath = sharedPrefs.getString(Common.INSTANCE.getPREVIOUS_PATH());
|
|
177
|
+
if (!oldPath.equals("")) {
|
|
178
|
+
boolean isDeleted = deleteOldBundleIfneeded(Common.INSTANCE.getPATH());
|
|
179
|
+
if (isDeleted) {
|
|
180
|
+
sharedPrefs.putString(Common.INSTANCE.getPATH(), oldPath);
|
|
181
|
+
sharedPrefs.putString(Common.INSTANCE.getPREVIOUS_PATH(), "");
|
|
182
|
+
promise.resolve(true);
|
|
183
|
+
} else {
|
|
184
|
+
promise.resolve(false);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
promise.resolve(false);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
@ReactMethod
|
|
150
192
|
public void getCurrentVersion(Promise promise) {
|
|
151
193
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
152
194
|
String version = sharedPrefs.getString(Common.INSTANCE.getVERSION());
|
|
@@ -159,13 +201,13 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
159
201
|
}
|
|
160
202
|
@ReactMethod
|
|
161
203
|
public void setCurrentVersion(String version, Promise promise) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
205
|
+
sharedPrefs.putString(Common.INSTANCE.getVERSION(), version);
|
|
206
|
+
promise.resolve(true);
|
|
165
207
|
}
|
|
166
208
|
|
|
167
209
|
@ReactMethod
|
|
168
|
-
|
|
210
|
+
public void setExactBundlePath(String path, Promise promise) {
|
|
169
211
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
170
212
|
sharedPrefs.putString(Common.INSTANCE.getPATH(), path);
|
|
171
213
|
PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
|
|
@@ -175,7 +217,7 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
175
217
|
}
|
|
176
218
|
sharedPrefs.putString(Common.INSTANCE.getCURRENT_VERSION_NAME(), latestVer);
|
|
177
219
|
promise.resolve(true);
|
|
178
|
-
|
|
220
|
+
}
|
|
179
221
|
|
|
180
222
|
@NonNull
|
|
181
223
|
@Override
|
package/ios/RNhotupdate.m
CHANGED
|
@@ -11,13 +11,14 @@ RCT_EXPORT_MODULE()
|
|
|
11
11
|
return [fileManager fileExistsAtPath:path];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
- (BOOL)removeBundleIfNeeded {
|
|
14
|
+
- (BOOL)removeBundleIfNeeded:(NSString *)pathKey {
|
|
15
|
+
NSString *keyToUse = pathKey ? pathKey : @"OLD_PATH";
|
|
15
16
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
16
|
-
NSString *retrievedString = [defaults stringForKey
|
|
17
|
+
NSString *retrievedString = [defaults stringForKey:keyToUse];
|
|
17
18
|
NSError *error = nil;
|
|
18
19
|
if (retrievedString && [self isFilePathValid:retrievedString]) {
|
|
19
20
|
BOOL isDeleted = [self deleteAllContentsOfParentDirectoryOfFile:retrievedString error:&error];
|
|
20
|
-
[defaults removeObjectForKey
|
|
21
|
+
[defaults removeObjectForKey:keyToUse];
|
|
21
22
|
[defaults synchronize];
|
|
22
23
|
return isDeleted;
|
|
23
24
|
} else {
|
|
@@ -130,6 +131,34 @@ RCT_EXPORT_MODULE()
|
|
|
130
131
|
|
|
131
132
|
return nil;
|
|
132
133
|
}
|
|
134
|
+
- (NSString *)renameExtractedFolderInDirectory:(NSString *)directoryPath {
|
|
135
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
136
|
+
NSError *error = nil;
|
|
137
|
+
|
|
138
|
+
// Get the contents of the extracted directory
|
|
139
|
+
NSArray *contents = [fileManager contentsOfDirectoryAtPath:directoryPath error:&error];
|
|
140
|
+
if (error || contents.count != 1) {
|
|
141
|
+
NSLog(@"Error retrieving extracted folder or unexpected structure: %@", error.localizedDescription);
|
|
142
|
+
return nil;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get the original extracted folder name (assuming only one folder exists)
|
|
146
|
+
NSString *originalFolderName = contents.firstObject;
|
|
147
|
+
NSString *originalFolderPath = [directoryPath stringByAppendingPathComponent:originalFolderName];
|
|
148
|
+
|
|
149
|
+
// Generate new folder name with timestamp
|
|
150
|
+
NSString *timestamp = [NSString stringWithFormat:@"output_%ld", (long)[[NSDate date] timeIntervalSince1970]];
|
|
151
|
+
NSString *newFolderPath = [directoryPath stringByAppendingPathComponent:timestamp];
|
|
152
|
+
|
|
153
|
+
// Rename the extracted folder
|
|
154
|
+
if (![fileManager moveItemAtPath:originalFolderPath toPath:newFolderPath error:&error]) {
|
|
155
|
+
NSLog(@"Failed to rename folder: %@", error.localizedDescription);
|
|
156
|
+
return nil;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
NSLog(@"Renamed extracted folder to: %@", newFolderPath);
|
|
160
|
+
return newFolderPath;
|
|
161
|
+
}
|
|
133
162
|
- (NSString *)unzipFileAtPath:(NSString *)zipFilePath extension:(NSString *)extension {
|
|
134
163
|
// Define the directory where the files will be extracted
|
|
135
164
|
NSString *extractedFolderPath = [[zipFilePath stringByDeletingPathExtension] stringByAppendingPathExtension:@"unzip"];
|
|
@@ -153,8 +182,13 @@ RCT_EXPORT_MODULE()
|
|
|
153
182
|
NSLog(@"Failed to unzip file");
|
|
154
183
|
return nil;
|
|
155
184
|
}
|
|
185
|
+
// Try renaming the extracted folder
|
|
186
|
+
NSString *renamedFolderPath = [self renameExtractedFolderInDirectory:extractedFolderPath];
|
|
187
|
+
|
|
188
|
+
// If renaming fails, use the original extracted folder path
|
|
189
|
+
NSString *finalFolderPath = renamedFolderPath ? renamedFolderPath : extractedFolderPath;
|
|
156
190
|
// Find .jsbundle files in the extracted directory
|
|
157
|
-
|
|
191
|
+
NSString *jsbundleFilePath = [self searchForJsBundleInDirectory:finalFolderPath extension:extension];
|
|
158
192
|
|
|
159
193
|
// Delete the zip file after extraction
|
|
160
194
|
NSError *removeError = nil;
|
|
@@ -170,12 +204,16 @@ RCT_EXPORT_MODULE()
|
|
|
170
204
|
// Expose setupBundlePath method to JavaScript
|
|
171
205
|
RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extension withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) {
|
|
172
206
|
if ([self isFilePathValid:path]) {
|
|
173
|
-
[self removeBundleIfNeeded];
|
|
207
|
+
[self removeBundleIfNeeded:nil];
|
|
174
208
|
//Unzip file
|
|
175
209
|
NSString *extractedFilePath = [self unzipFileAtPath:path extension:(extension != nil) ? extension : @".jsbundle"];
|
|
176
210
|
if (extractedFilePath) {
|
|
177
211
|
NSLog(@"file extraction----- %@", extractedFilePath);
|
|
178
212
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
213
|
+
NSString *oldPath = [defaults stringForKey:@"PATH"];
|
|
214
|
+
if (oldPath) {
|
|
215
|
+
[defaults setObject:oldPath forKey:@"OLD_PATH"];
|
|
216
|
+
}
|
|
179
217
|
[defaults setObject:extractedFilePath forKey:@"PATH"];
|
|
180
218
|
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
|
|
181
219
|
[defaults synchronize];
|
|
@@ -190,10 +228,34 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
190
228
|
|
|
191
229
|
// Expose deleteBundle method to JavaScript
|
|
192
230
|
RCT_EXPORT_METHOD(deleteBundle:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) {
|
|
193
|
-
|
|
194
|
-
|
|
231
|
+
BOOL isDeleted = [self removeBundleIfNeeded:@"PATH"];
|
|
232
|
+
BOOL isDeletedOld = [self removeBundleIfNeeded:nil];
|
|
233
|
+
if (isDeleted && isDeletedOld) {
|
|
234
|
+
resolve(@(YES));
|
|
235
|
+
} else {
|
|
236
|
+
resolve(@(NO));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Expose deleteBundle method to JavaScript
|
|
240
|
+
RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
241
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
242
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
243
|
+
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
244
|
+
NSString *oldPath = [defaults stringForKey:@"OLD_PATH"];
|
|
245
|
+
if (oldPath && [self isFilePathValid:oldPath]) {
|
|
246
|
+
BOOL isDeleted = [self removeBundleIfNeeded:@"PATH"];
|
|
247
|
+
if (isDeleted) {
|
|
248
|
+
[defaults setObject:oldPath forKey:@"PATH"];
|
|
249
|
+
[defaults removeObjectForKey:@"OLD_PATH"];
|
|
250
|
+
[defaults synchronize];
|
|
251
|
+
resolve(@(YES));
|
|
252
|
+
} else {
|
|
253
|
+
resolve(@(NO));
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
resolve(@(NO));
|
|
257
|
+
}
|
|
195
258
|
}
|
|
196
|
-
|
|
197
259
|
RCT_EXPORT_METHOD(getCurrentVersion:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) {
|
|
198
260
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
199
261
|
NSString *version = [defaults stringForKey:@"VERSION"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-ota-hot-update",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Hot update for react native",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"repository": "https://github.com/vantuan88291/react-native-ota-hot-update",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"homepage": "https://github.com/vantuan88291/react-native-ota-hot-update",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"buffer": "^6.0.3",
|
|
15
|
-
"isomorphic-git": "
|
|
15
|
+
"isomorphic-git": "1.27.3"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"react-native": ">=0.63.4",
|
package/src/index.tsx
CHANGED
|
@@ -46,6 +46,9 @@ function deleteBundlePath(): Promise<boolean> {
|
|
|
46
46
|
function getCurrentVersion(): Promise<string> {
|
|
47
47
|
return RNhotupdate.getCurrentVersion();
|
|
48
48
|
}
|
|
49
|
+
function rollbackToPreviousBundle(): Promise<boolean> {
|
|
50
|
+
return RNhotupdate.rollbackToPreviousBundle();
|
|
51
|
+
}
|
|
49
52
|
async function getVersionAsNumber() {
|
|
50
53
|
const rawVersion = await getCurrentVersion();
|
|
51
54
|
return +rawVersion;
|
|
@@ -168,6 +171,7 @@ export default {
|
|
|
168
171
|
resetApp,
|
|
169
172
|
getCurrentVersion: getVersionAsNumber,
|
|
170
173
|
setCurrentVersion,
|
|
174
|
+
rollbackToPreviousBundle,
|
|
171
175
|
git: {
|
|
172
176
|
checkForGitUpdate,
|
|
173
177
|
...git,
|