react-native-ota-hot-update 1.0.9 → 1.1.1
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/README.md
CHANGED
|
@@ -36,6 +36,31 @@ Open `AppDelegate.m` and add this:
|
|
|
36
36
|
#endif
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
|
+
#### For downloading in background mode on IOS, following this (optional):
|
|
40
|
+
|
|
41
|
+
AppDelegate.h:
|
|
42
|
+
```bash
|
|
43
|
+
@property (nonatomic, assign) UIBackgroundTaskIdentifier taskIdentifier;
|
|
44
|
+
```
|
|
45
|
+
AppDelegate.mm:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
- (void)applicationWillResignActive:(UIApplication *)application {
|
|
49
|
+
if (self.taskIdentifier != UIBackgroundTaskInvalid) {
|
|
50
|
+
[application endBackgroundTask:self.taskIdentifier];
|
|
51
|
+
self.taskIdentifier = UIBackgroundTaskInvalid;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
__weak AppDelegate *weakSelf = self;
|
|
55
|
+
self.taskIdentifier = [application beginBackgroundTaskWithName:nil expirationHandler:^{
|
|
56
|
+
if (weakSelf) {
|
|
57
|
+
[application endBackgroundTask:weakSelf.taskIdentifier];
|
|
58
|
+
weakSelf.taskIdentifier = UIBackgroundTaskInvalid;
|
|
59
|
+
}
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
39
64
|
|
|
40
65
|
### Android
|
|
41
66
|
Open `MainApplication.java/kt` and add these codes bellow:
|
|
@@ -53,12 +78,24 @@ override fun getJSBundleFile(): String? {
|
|
|
53
78
|
Here is the guideline to control bundle js by yourself, in here i am using Firebase storage to store bundlejs file and a json file that announce new version is comming:
|
|
54
79
|
|
|
55
80
|
#### 1.Add these script into your package.json to export bundlejs file:
|
|
81
|
+
|
|
82
|
+
- For react native CLI:
|
|
56
83
|
```bash
|
|
57
84
|
"scripts": {
|
|
58
85
|
"export-android": "mkdir -p android/output && react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/output/index.android.bundle --assets-dest android/output && cd android && find output -type f | zip index.android.bundle.zip -@ && cd .. && rm -rf android/output",
|
|
59
86
|
"export-ios": "mkdir -p ios/output && react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/output/main.jsbundle --assets-dest ios/output && cd ios && find output -type f | zip main.jsbundle.zip -@ && cd .. && rm -rf ios/output"
|
|
60
87
|
}
|
|
61
88
|
```
|
|
89
|
+
- For expo / expo bare project:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
"scripts": {
|
|
93
|
+
"export-android": "mkdir -p android/output && npx expo export:embed --platform android --entry-file node_modules/expo/AppEntry.js --bundle-output android/output/index.android.bundle --dev false --assets-dest android/output && cd android && find output -type f | zip index.android.bundle.zip -@ && cd .. && rm -rf android/output",
|
|
94
|
+
"export-ios": "mkdir -p ios/output && npx expo export:embed --platform ios --entry-file node_modules/expo/AppEntry.js --bundle-output ios/output/main.jsbundle --dev false --assets-dest ios/output && cd ios && find output -type f | zip main.jsbundle.zip -@ && cd .. && rm -rf ios/output"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
For expo you might need check path of `--entry-file node_modules/expo/AppEntry.js`, get it from package.json / main
|
|
98
|
+
|
|
62
99
|
These commands are export bundle file and compress it as a zip file, one for android and one for ios. You can create your own script that export and auto upload to your server.
|
|
63
100
|
|
|
64
101
|
Then create an json file: `update.json` like that:
|
|
@@ -100,6 +137,18 @@ After you have done everything related to version manager, you just handle the w
|
|
|
100
137
|
|
|
101
138
|
The important thing: this library will control `version` by it self, need always pass version as parameter in `downloadBundleUri`, it will storage as a cache and use this to check whether need update version in the next time. Default of `version` is **0**
|
|
102
139
|
|
|
140
|
+
## Tips for manage bundles
|
|
141
|
+
|
|
142
|
+
In this demo project i have used firebase storage to control the bundles and version, but that is not useful. I would like to suggest you to use some CMS to control the bundles.
|
|
143
|
+
For CMS, it has a lot open sources, now i am using strapi CMS to control the version, i also create auto release script like code push and can integrate with CI/CD. Here is some screenshot of strapi CMS:
|
|
144
|
+
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+

|
|
148
|
+
|
|
149
|
+
Beside strapi you can also try craftcms, payloadcms...
|
|
150
|
+
You can refer folder sampleComponent, it has the logic of update with CMS and also include script auto upload the bundle to CMS.
|
|
151
|
+
Contact me via email if you want to implement hot update via CMS (need service fee)
|
|
103
152
|
|
|
104
153
|
## Functions
|
|
105
154
|
|
|
@@ -122,6 +171,7 @@ The important thing: this library will control `version` by it self, need always
|
|
|
122
171
|
| updateFail(message: string) | No | Callback | Will trigger when install update failed |
|
|
123
172
|
| restartAfterInstall | No | boolean | default is `false`, if `true` will restart the app after install success to apply the new change |
|
|
124
173
|
| progress | No | void | A callback to show progress when downloading bundle |
|
|
174
|
+
| extensionBundle | No | string | extension of bundle js file, default is .bundle for android, ios is .jsbundle |
|
|
125
175
|
|
|
126
176
|
## DownloadManager
|
|
127
177
|
|
|
@@ -50,7 +50,7 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
private String unzip(File zipFile) {
|
|
53
|
+
private String unzip(File zipFile, String extension) {
|
|
54
54
|
File destDir = zipFile.getParentFile(); // Directory of the zip file
|
|
55
55
|
|
|
56
56
|
String bundleFilePath = null;
|
|
@@ -78,7 +78,7 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
if (newFile.getAbsolutePath().contains(
|
|
81
|
+
if (newFile.getAbsolutePath().contains(extension)) {
|
|
82
82
|
bundleFilePath = newFile.getAbsolutePath();
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -90,12 +90,12 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
@ReactMethod
|
|
93
|
-
public void setupBundlePath(String path, Promise promise) {
|
|
93
|
+
public void setupBundlePath(String path, String extension, Promise promise) {
|
|
94
94
|
if (path != null) {
|
|
95
95
|
deleteOldBundleIfneeded();
|
|
96
96
|
File file = new File(path);
|
|
97
97
|
if (file.exists() && file.isFile()) {
|
|
98
|
-
String fileUnzip = unzip(file);
|
|
98
|
+
String fileUnzip = unzip(file, extension != null ? extension : ".bundle");
|
|
99
99
|
Log.d("setupBundlePath: ", fileUnzip);
|
|
100
100
|
if (fileUnzip != null) {
|
|
101
101
|
file.delete();
|
package/ios/RNhotupdate.m
CHANGED
|
@@ -97,7 +97,7 @@ RCT_EXPORT_MODULE()
|
|
|
97
97
|
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
- (NSString *)searchForJsBundleInDirectory:(NSString *)directoryPath {
|
|
100
|
+
- (NSString *)searchForJsBundleInDirectory:(NSString *)directoryPath extension:(NSString *)extension {
|
|
101
101
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
102
102
|
NSError *error;
|
|
103
103
|
|
|
@@ -114,11 +114,11 @@ RCT_EXPORT_MODULE()
|
|
|
114
114
|
if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
|
|
115
115
|
if (isDirectory) {
|
|
116
116
|
// Recursively search in subdirectories
|
|
117
|
-
NSString *foundPath = [self searchForJsBundleInDirectory:filePath];
|
|
117
|
+
NSString *foundPath = [self searchForJsBundleInDirectory:filePath extension:extension];
|
|
118
118
|
if (foundPath) {
|
|
119
119
|
return foundPath;
|
|
120
120
|
}
|
|
121
|
-
} else if ([filePath hasSuffix
|
|
121
|
+
} else if ([filePath hasSuffix:extension]) {
|
|
122
122
|
// Return the path if it's a .jsbundle file
|
|
123
123
|
return filePath;
|
|
124
124
|
}
|
|
@@ -127,7 +127,7 @@ RCT_EXPORT_MODULE()
|
|
|
127
127
|
|
|
128
128
|
return nil;
|
|
129
129
|
}
|
|
130
|
-
- (NSString *)unzipFileAtPath:(NSString *)zipFilePath {
|
|
130
|
+
- (NSString *)unzipFileAtPath:(NSString *)zipFilePath extension:(NSString *)extension {
|
|
131
131
|
// Define the directory where the files will be extracted
|
|
132
132
|
NSString *extractedFolderPath = [[zipFilePath stringByDeletingPathExtension] stringByAppendingPathExtension:@"unzip"];
|
|
133
133
|
|
|
@@ -151,7 +151,7 @@ RCT_EXPORT_MODULE()
|
|
|
151
151
|
return nil;
|
|
152
152
|
}
|
|
153
153
|
// Find .jsbundle files in the extracted directory
|
|
154
|
-
|
|
154
|
+
NSString *jsbundleFilePath = [self searchForJsBundleInDirectory:extractedFolderPath extension:extension];
|
|
155
155
|
|
|
156
156
|
// Delete the zip file after extraction
|
|
157
157
|
NSError *removeError = nil;
|
|
@@ -165,11 +165,11 @@ RCT_EXPORT_MODULE()
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// Expose setupBundlePath method to JavaScript
|
|
168
|
-
RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) {
|
|
168
|
+
RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extension withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) {
|
|
169
169
|
if ([self isFilePathValid:path]) {
|
|
170
170
|
[self removeBundleIfNeeded];
|
|
171
171
|
//Unzip file
|
|
172
|
-
NSString *extractedFilePath = [self unzipFileAtPath:path];
|
|
172
|
+
NSString *extractedFilePath = [self unzipFileAtPath:path extension:(extension != nil) ? extension : @".jsbundle"];
|
|
173
173
|
NSLog(@"file extraction----- %@", extractedFilePath);
|
|
174
174
|
if (extractedFilePath) {
|
|
175
175
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { NativeModules, Platform } from 'react-native';
|
|
2
2
|
import {DownloadManager} from './download';
|
|
3
3
|
const LINKING_ERROR =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
'The package \'rn-hotupdate\' doesn\'t seem to be linked. Make sure: \n\n' +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go\n';
|
|
8
8
|
|
|
9
9
|
export interface UpdateOption {
|
|
10
10
|
headers?: object
|
|
@@ -12,21 +12,23 @@ export interface UpdateOption {
|
|
|
12
12
|
updateSuccess?(): void
|
|
13
13
|
updateFail?(message?: string): void
|
|
14
14
|
restartAfterInstall?: boolean
|
|
15
|
+
extensionBundle?: string,
|
|
15
16
|
}
|
|
16
17
|
const RNhotupdate = NativeModules.RNhotupdate
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
? NativeModules.RNhotupdate
|
|
19
|
+
: new Proxy(
|
|
20
|
+
{},
|
|
21
|
+
{
|
|
22
|
+
get() {
|
|
23
|
+
throw new Error(LINKING_ERROR);
|
|
24
|
+
},
|
|
25
|
+
}
|
|
25
26
|
);
|
|
26
27
|
const downloadBundleFile = async (downloadManager: DownloadManager, uri: string, headers?: object, callback?: (received: string, total: string) => void) => {
|
|
27
28
|
const res = await downloadManager
|
|
28
29
|
.config({
|
|
29
30
|
fileCache: Platform.OS === 'android',
|
|
31
|
+
IOSBackgroundTask: true,
|
|
30
32
|
})
|
|
31
33
|
.fetch('GET', uri, {
|
|
32
34
|
...headers,
|
|
@@ -38,8 +40,8 @@ const downloadBundleFile = async (downloadManager: DownloadManager, uri: string,
|
|
|
38
40
|
});
|
|
39
41
|
return res.path();
|
|
40
42
|
};
|
|
41
|
-
function setupBundlePath(path: string): Promise<boolean> {
|
|
42
|
-
return RNhotupdate.setupBundlePath(path);
|
|
43
|
+
function setupBundlePath(path: string, extension?: string): Promise<boolean> {
|
|
44
|
+
return RNhotupdate.setupBundlePath(path, extension);
|
|
43
45
|
}
|
|
44
46
|
function deleteBundlePath(): Promise<boolean> {
|
|
45
47
|
return RNhotupdate.deleteBundle();
|
|
@@ -90,7 +92,7 @@ async function downloadBundleUri(downloadManager: DownloadManager, uri: string,
|
|
|
90
92
|
try {
|
|
91
93
|
const path = await downloadBundleFile(downloadManager, uri, option?.headers, option?.progress);
|
|
92
94
|
if (path) {
|
|
93
|
-
setupBundlePath(path).then(success => {
|
|
95
|
+
setupBundlePath(path, option?.extensionBundle).then(success => {
|
|
94
96
|
if (success) {
|
|
95
97
|
setCurrentVersion(version);
|
|
96
98
|
option?.updateSuccess?.();
|