react-native-ota-hot-update 1.1.3 → 1.1.5
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/src/main/java/com/rnhotupdate/HotUpdateModule.java +20 -0
- package/android/src/main/java/com/rnhotupdate/OtaHotUpdate.java +21 -1
- package/android/src/main/java/com/rnhotupdate/SharedPrefs.kt +1 -0
- package/ios/RNhotupdate.m +19 -1
- package/package.json +7 -2
- package/src/gits/helper/fileReader.js +44 -0
- package/src/gits/helper/fs.ts +154 -0
- package/src/gits/index.ts +118 -0
- package/src/index.tsx +66 -9
- package/src/type.ts +82 -0
|
@@ -2,6 +2,7 @@ package com.rnhotupdate;
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.content.Intent;
|
|
5
|
+
import android.content.pm.PackageInfo;
|
|
5
6
|
import android.util.Log;
|
|
6
7
|
|
|
7
8
|
import com.facebook.react.bridge.Promise;
|
|
@@ -101,6 +102,12 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
101
102
|
file.delete();
|
|
102
103
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
103
104
|
sharedPrefs.putString(Common.INSTANCE.getPATH(), fileUnzip);
|
|
105
|
+
PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
|
|
106
|
+
String latestVer = null;
|
|
107
|
+
if (info != null) {
|
|
108
|
+
latestVer = info.versionName;
|
|
109
|
+
}
|
|
110
|
+
sharedPrefs.putString(Common.INSTANCE.getCURRENT_VERSION_NAME(), latestVer);
|
|
104
111
|
promise.resolve(true);
|
|
105
112
|
} else {
|
|
106
113
|
file.delete();
|
|
@@ -157,6 +164,19 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
157
164
|
promise.resolve(true);
|
|
158
165
|
}
|
|
159
166
|
|
|
167
|
+
@ReactMethod
|
|
168
|
+
public void setExactBundlePath(String path, Promise promise) {
|
|
169
|
+
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
170
|
+
sharedPrefs.putString(Common.INSTANCE.getPATH(), path);
|
|
171
|
+
PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
|
|
172
|
+
String latestVer = null;
|
|
173
|
+
if (info != null) {
|
|
174
|
+
latestVer = info.versionName;
|
|
175
|
+
}
|
|
176
|
+
sharedPrefs.putString(Common.INSTANCE.getCURRENT_VERSION_NAME(), latestVer);
|
|
177
|
+
promise.resolve(true);
|
|
178
|
+
}
|
|
179
|
+
|
|
160
180
|
@NonNull
|
|
161
181
|
@Override
|
|
162
182
|
public String getName() {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package com.rnhotupdate;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.content.pm.PackageInfo;
|
|
5
|
+
import android.content.pm.PackageManager;
|
|
6
|
+
import android.os.Build;
|
|
4
7
|
|
|
5
8
|
import com.facebook.react.ReactPackage;
|
|
6
9
|
import com.facebook.react.bridge.NativeModule;
|
|
@@ -31,13 +34,30 @@ public class OtaHotUpdate implements ReactPackage {
|
|
|
31
34
|
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
|
|
32
35
|
return Collections.emptyList();
|
|
33
36
|
}
|
|
37
|
+
public static PackageInfo packageInfo (Context context) {
|
|
38
|
+
try {
|
|
39
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
40
|
+
return context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(0));
|
|
41
|
+
} else {
|
|
42
|
+
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
|
43
|
+
}
|
|
44
|
+
} catch (Exception e) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
34
48
|
public static String getBundleJS() {
|
|
35
49
|
if (mContext == null) {
|
|
36
50
|
return Common.INSTANCE.getDEFAULT_BUNDLE();
|
|
37
51
|
}
|
|
38
52
|
SharedPrefs sharedPrefs = new SharedPrefs(mContext);
|
|
53
|
+
PackageInfo info = OtaHotUpdate.packageInfo(mContext);
|
|
54
|
+
String latestVer = null;
|
|
55
|
+
if (info != null) {
|
|
56
|
+
latestVer = info.versionName;
|
|
57
|
+
}
|
|
39
58
|
String pathBundle = sharedPrefs.getString(Common.INSTANCE.getPATH());
|
|
40
|
-
|
|
59
|
+
String currentVer = sharedPrefs.getString(Common.INSTANCE.getCURRENT_VERSION_NAME());
|
|
60
|
+
if (pathBundle.equals("") || (info != null && !currentVer.equals("") && !latestVer.equals(currentVer))) {
|
|
41
61
|
return Common.INSTANCE.getDEFAULT_BUNDLE();
|
|
42
62
|
}
|
|
43
63
|
return pathBundle;
|
package/ios/RNhotupdate.m
CHANGED
|
@@ -90,7 +90,10 @@ RCT_EXPORT_MODULE()
|
|
|
90
90
|
+ (NSURL *)getBundle {
|
|
91
91
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
92
92
|
NSString *retrievedString = [defaults stringForKey:@"PATH"];
|
|
93
|
-
|
|
93
|
+
NSString *currentVersionName = [defaults stringForKey:@"VERSION_NAME"];
|
|
94
|
+
NSString *versionName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
|
95
|
+
|
|
96
|
+
if (retrievedString && [self isFilePathExist:retrievedString] && [currentVersionName isEqualToString:versionName]) {
|
|
94
97
|
NSURL *fileURL = [NSURL fileURLWithPath:retrievedString];
|
|
95
98
|
return fileURL;
|
|
96
99
|
} else {
|
|
@@ -174,6 +177,7 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
174
177
|
NSLog(@"file extraction----- %@", extractedFilePath);
|
|
175
178
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
176
179
|
[defaults setObject:extractedFilePath forKey:@"PATH"];
|
|
180
|
+
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
|
|
177
181
|
[defaults synchronize];
|
|
178
182
|
resolve(@(YES));
|
|
179
183
|
} else {
|
|
@@ -211,6 +215,20 @@ RCT_EXPORT_METHOD(setCurrentVersion:(NSString *)version withResolver:(RCTPromise
|
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
|
|
218
|
+
RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
219
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
220
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
221
|
+
if (path) {
|
|
222
|
+
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
223
|
+
[defaults setObject:path forKey:@"PATH"];
|
|
224
|
+
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
|
|
225
|
+
[defaults synchronize];
|
|
226
|
+
resolve(@(YES));
|
|
227
|
+
} else {
|
|
228
|
+
resolve(@(NO));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
- (void)loadBundle
|
|
215
233
|
{
|
|
216
234
|
RCTTriggerReloadCommandListeners(@"rn-hotupdate: Restart");
|
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.5",
|
|
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",
|
|
@@ -10,8 +10,13 @@
|
|
|
10
10
|
"url": "https://github.com/vantuan88291/react-native-ota-hot-update/issues"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://github.com/vantuan88291/react-native-ota-hot-update",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"buffer": "^6.0.3",
|
|
15
|
+
"isomorphic-git": "git+https://github.com/vantuan88291/isomorphic-git.git"
|
|
16
|
+
},
|
|
13
17
|
"peerDependencies": {
|
|
14
|
-
"react-native": ">=0.63.4"
|
|
18
|
+
"react-native": ">=0.63.4",
|
|
19
|
+
"react-native-fs": "*"
|
|
15
20
|
},
|
|
16
21
|
"create-react-native-library": {
|
|
17
22
|
"type": "module-legacy",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @format
|
|
3
|
+
*/
|
|
4
|
+
FileReader.prototype.readAsArrayBuffer = function (blob) {
|
|
5
|
+
if (this.readyState === this.LOADING) throw new Error('InvalidStateError');
|
|
6
|
+
this._setReadyState(this.LOADING);
|
|
7
|
+
this._result = null;
|
|
8
|
+
this._error = null;
|
|
9
|
+
const fr = new FileReader();
|
|
10
|
+
fr.onloadend = () => {
|
|
11
|
+
const content = atob(fr.result.replace(/data:[^;]+;base64,/, ''));
|
|
12
|
+
const buffer = new ArrayBuffer(content.length);
|
|
13
|
+
const view = new Uint8Array(buffer);
|
|
14
|
+
view.set(Array.from(content).map((c) => c.charCodeAt(0)));
|
|
15
|
+
this._result = buffer;
|
|
16
|
+
this._setReadyState(this.DONE);
|
|
17
|
+
};
|
|
18
|
+
fr.readAsDataURL(blob);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// from: https://stackoverflow.com/questions/42829838/react-native-atob-btoa-not-working-without-remote-js-debugging
|
|
22
|
+
const chars =
|
|
23
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
24
|
+
const atob = (input = '') => {
|
|
25
|
+
let str = input.replace(/[=]+$/, '');
|
|
26
|
+
let output = '';
|
|
27
|
+
|
|
28
|
+
if (str.length % 4 == 1) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"'atob' failed: The string to be decoded is not correctly encoded."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
for (
|
|
34
|
+
let bc = 0, bs = 0, buffer, i = 0;
|
|
35
|
+
(buffer = str.charAt(i++));
|
|
36
|
+
~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4)
|
|
37
|
+
? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
|
|
38
|
+
: 0
|
|
39
|
+
) {
|
|
40
|
+
buffer = chars.indexOf(buffer);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return output;
|
|
44
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
|
|
3
|
+
let RNFS = {
|
|
4
|
+
unlink: console.log,
|
|
5
|
+
readdir: console.log,
|
|
6
|
+
mkdir: console.log,
|
|
7
|
+
readFile: console.log,
|
|
8
|
+
writeFile: console.log,
|
|
9
|
+
stat: console.log,
|
|
10
|
+
};
|
|
11
|
+
try {
|
|
12
|
+
RNFS = require('react-native-fs');
|
|
13
|
+
} catch {}
|
|
14
|
+
|
|
15
|
+
function Err(name: string) {
|
|
16
|
+
return class extends Error {
|
|
17
|
+
public code = name;
|
|
18
|
+
constructor(...args: any) {
|
|
19
|
+
super(...args);
|
|
20
|
+
if (this.message) {
|
|
21
|
+
this.message = name + ': ' + this.message;
|
|
22
|
+
} else {
|
|
23
|
+
this.message = name;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// const EEXIST = Err('EEXIST'); // <-- Unused because RNFS's mkdir never throws
|
|
30
|
+
const ENOENT = Err('ENOENT');
|
|
31
|
+
const ENOTDIR = Err('ENOTDIR');
|
|
32
|
+
// const ENOTEMPTY = Err('ENOTEMPTY'); // <-- Unused because RNFS's unlink is recursive by default
|
|
33
|
+
|
|
34
|
+
export const readdir = async (path: string) => {
|
|
35
|
+
try {
|
|
36
|
+
return await RNFS.readdir(path);
|
|
37
|
+
} catch (err: any) {
|
|
38
|
+
switch (err.message) {
|
|
39
|
+
case 'Attempt to get length of null array': {
|
|
40
|
+
throw new ENOTDIR(path);
|
|
41
|
+
}
|
|
42
|
+
case 'Folder does not exist': {
|
|
43
|
+
throw new ENOENT(path);
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const mkdir = async (path: string) => {
|
|
52
|
+
return RNFS.mkdir(path);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const readFile = async (
|
|
56
|
+
path: string,
|
|
57
|
+
opts?: string | { [key: string]: string }
|
|
58
|
+
) => {
|
|
59
|
+
let encoding;
|
|
60
|
+
|
|
61
|
+
if (typeof opts === 'string') {
|
|
62
|
+
encoding = opts;
|
|
63
|
+
} else if (typeof opts === 'object') {
|
|
64
|
+
encoding = opts.encoding;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
let result: string | Uint8Array = await RNFS.readFile(
|
|
69
|
+
path,
|
|
70
|
+
encoding || 'base64'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!encoding) {
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
result = Buffer.from(result, 'base64');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
export const writeFile = async (
|
|
81
|
+
path: string,
|
|
82
|
+
content: string | Uint8Array,
|
|
83
|
+
opts?: string | { [key: string]: string }
|
|
84
|
+
) => {
|
|
85
|
+
let encoding;
|
|
86
|
+
|
|
87
|
+
if (typeof opts === 'string') {
|
|
88
|
+
encoding = opts;
|
|
89
|
+
} else if (typeof opts === 'object') {
|
|
90
|
+
encoding = opts.encoding;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof content === 'string') {
|
|
94
|
+
encoding = encoding || 'utf8';
|
|
95
|
+
} else {
|
|
96
|
+
encoding = 'base64';
|
|
97
|
+
content = Buffer.from(content).toString('base64');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await RNFS.writeFile(path, content as string, encoding);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const stat = async (path: string) => {
|
|
104
|
+
try {
|
|
105
|
+
const r = await RNFS.stat(path);
|
|
106
|
+
// we monkeypatch the result with a `isSymbolicLink` method because isomorphic-git needs it.
|
|
107
|
+
// Since RNFS doesn't appear to support symlinks at all, we'll just always return false.
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
r.isSymbolicLink = () => false;
|
|
110
|
+
return r;
|
|
111
|
+
} catch (err: any) {
|
|
112
|
+
switch (err.message) {
|
|
113
|
+
case 'File does not exist': {
|
|
114
|
+
throw new ENOENT(path);
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Since there are no symbolic links, lstat and stat are equivalent
|
|
123
|
+
export const lstat = stat;
|
|
124
|
+
|
|
125
|
+
export const unlink = async (path: string) => {
|
|
126
|
+
try {
|
|
127
|
+
await RNFS.unlink(path);
|
|
128
|
+
} catch (err: any) {
|
|
129
|
+
switch (err.message) {
|
|
130
|
+
case 'File does not exist': {
|
|
131
|
+
throw new ENOENT(path);
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// RNFS doesn't have a separate rmdir method, so we can use unlink for deleting directories too
|
|
140
|
+
export const rmdir = unlink;
|
|
141
|
+
|
|
142
|
+
// These are optional, which is good because there is no equivalent in RNFS
|
|
143
|
+
export const readlink = async () => {
|
|
144
|
+
throw new Error('not implemented');
|
|
145
|
+
};
|
|
146
|
+
export const symlink = async () => {
|
|
147
|
+
throw new Error('not implemented');
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Technically we could pull this off by using `readFile` + `writeFile` with the `mode` option
|
|
151
|
+
// However, it's optional, because isomorphic-git will do exactly that (a readFile and a writeFile with the new mode)
|
|
152
|
+
export const chmod = async () => {
|
|
153
|
+
throw new Error('not implemented');
|
|
154
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import './helper/fileReader.js';
|
|
2
|
+
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import git, { PromiseFsClient } from 'isomorphic-git/index.umd.min.js';
|
|
5
|
+
import http from 'isomorphic-git/http/web/index.js';
|
|
6
|
+
import * as promises from './helper/fs';
|
|
7
|
+
import type { CloneOption, PullOption } from '../type';
|
|
8
|
+
|
|
9
|
+
const fs: PromiseFsClient = { promises };
|
|
10
|
+
const getFolder = (folderName?: string) => {
|
|
11
|
+
try {
|
|
12
|
+
const { DocumentDirectoryPath } = require('react-native-fs');
|
|
13
|
+
return DocumentDirectoryPath + (folderName || '/git_hot_update');
|
|
14
|
+
} catch (e) {}
|
|
15
|
+
return '';
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Should set config after clone success, otherwise cannot pull
|
|
19
|
+
*/
|
|
20
|
+
const setConfig = async (
|
|
21
|
+
folderName?: string,
|
|
22
|
+
options?: {
|
|
23
|
+
userName?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
}
|
|
26
|
+
) => {
|
|
27
|
+
await git.setConfig({
|
|
28
|
+
fs,
|
|
29
|
+
dir: getFolder(folderName),
|
|
30
|
+
path: options?.userName || 'user.name',
|
|
31
|
+
value: options?.email || 'hotupdate',
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const cloneRepo = async (options: CloneOption) => {
|
|
35
|
+
try {
|
|
36
|
+
await git.clone({
|
|
37
|
+
fs,
|
|
38
|
+
http,
|
|
39
|
+
dir: getFolder(options?.folderName),
|
|
40
|
+
url: options?.url,
|
|
41
|
+
singleBranch: true,
|
|
42
|
+
depth: 1,
|
|
43
|
+
ref: options?.branch,
|
|
44
|
+
onProgress({ loaded, total }: { loaded: number; total: number }) {
|
|
45
|
+
if (options?.onProgress && total > 0) {
|
|
46
|
+
options?.onProgress(loaded, total);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
await setConfig(options?.folderName, {
|
|
51
|
+
email: options?.email,
|
|
52
|
+
userName: options?.userName,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
msg: null,
|
|
57
|
+
bundle: `${getFolder(options?.folderName)}/${options.bundlePath}`,
|
|
58
|
+
};
|
|
59
|
+
} catch (e: any) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
msg: e.toString(),
|
|
63
|
+
bundle: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const pullUpdate = async (options: PullOption) => {
|
|
68
|
+
try {
|
|
69
|
+
let count = 0;
|
|
70
|
+
await git.pull({
|
|
71
|
+
fs,
|
|
72
|
+
http,
|
|
73
|
+
dir: getFolder(options?.folderName),
|
|
74
|
+
ref: options?.branch,
|
|
75
|
+
singleBranch: true,
|
|
76
|
+
onProgress({ loaded, total }: { loaded: number; total: number }) {
|
|
77
|
+
if (total > 0) {
|
|
78
|
+
count = total;
|
|
79
|
+
if (options?.onProgress) {
|
|
80
|
+
options?.onProgress(loaded, total);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
success: count > 0,
|
|
87
|
+
msg: count > 0 ? 'Pull success' : 'No updated',
|
|
88
|
+
};
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
console.log(e.toString());
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
msg: e.toString(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const getBranchName = async (folderName?: string) => {
|
|
98
|
+
try {
|
|
99
|
+
return await git.currentBranch({
|
|
100
|
+
fs,
|
|
101
|
+
dir: getFolder(folderName),
|
|
102
|
+
fullname: false,
|
|
103
|
+
});
|
|
104
|
+
} catch (e: any) {
|
|
105
|
+
console.log(e.toString());
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const removeGitUpdate = (folderName?: string) => {
|
|
110
|
+
fs.promises.unlink(getFolder(folderName));
|
|
111
|
+
};
|
|
112
|
+
export default {
|
|
113
|
+
cloneRepo,
|
|
114
|
+
pullUpdate,
|
|
115
|
+
getBranchName,
|
|
116
|
+
setConfig,
|
|
117
|
+
removeGitUpdate,
|
|
118
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { NativeModules, Platform } from 'react-native';
|
|
2
2
|
import {DownloadManager} from './download';
|
|
3
|
+
import { UpdateGitOption, UpdateOption } from './type';
|
|
4
|
+
import git from './gits';
|
|
5
|
+
|
|
3
6
|
const LINKING_ERROR =
|
|
4
7
|
'The package \'rn-hotupdate\' doesn\'t seem to be linked. Make sure: \n\n' +
|
|
5
8
|
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
9
|
'- You rebuilt the app after installing the package\n' +
|
|
7
10
|
'- You are not using Expo Go\n';
|
|
8
11
|
|
|
9
|
-
export interface UpdateOption {
|
|
10
|
-
headers?: object
|
|
11
|
-
progress?(received: string, total: string): void
|
|
12
|
-
updateSuccess?(): void
|
|
13
|
-
updateFail?(message?: string): void
|
|
14
|
-
restartAfterInstall?: boolean
|
|
15
|
-
extensionBundle?: string,
|
|
16
|
-
}
|
|
17
12
|
const RNhotupdate = NativeModules.RNhotupdate
|
|
18
13
|
? NativeModules.RNhotupdate
|
|
19
14
|
: new Proxy(
|
|
@@ -42,6 +37,9 @@ const downloadBundleFile = async (downloadManager: DownloadManager, uri: string,
|
|
|
42
37
|
function setupBundlePath(path: string, extension?: string): Promise<boolean> {
|
|
43
38
|
return RNhotupdate.setupBundlePath(path, extension);
|
|
44
39
|
}
|
|
40
|
+
function setupExactBundlePath(path: string): Promise<boolean> {
|
|
41
|
+
return RNhotupdate.setExactBundlePath(path);
|
|
42
|
+
}
|
|
45
43
|
function deleteBundlePath(): Promise<boolean> {
|
|
46
44
|
return RNhotupdate.deleteBundle();
|
|
47
45
|
}
|
|
@@ -111,12 +109,71 @@ async function downloadBundleUri(downloadManager: DownloadManager, uri: string,
|
|
|
111
109
|
installFail(option, e);
|
|
112
110
|
}
|
|
113
111
|
}
|
|
114
|
-
|
|
112
|
+
const checkForGitUpdate = async (options: UpdateGitOption) => {
|
|
113
|
+
try {
|
|
114
|
+
if (!options.url || !options.bundlePath) {
|
|
115
|
+
throw new Error(`url or bundlePath should not be null`);
|
|
116
|
+
}
|
|
117
|
+
const branch = await git.getBranchName();
|
|
118
|
+
if (branch) {
|
|
119
|
+
const pull = await git.pullUpdate({
|
|
120
|
+
branch,
|
|
121
|
+
onProgress: options?.onProgress,
|
|
122
|
+
folderName: options?.folderName,
|
|
123
|
+
});
|
|
124
|
+
if (pull.success) {
|
|
125
|
+
options?.onPullSuccess?.();
|
|
126
|
+
if (options?.restartAfterInstall) {
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
resetApp();
|
|
129
|
+
}, 300);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
options?.onPullFailed?.(pull.msg);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
const clone = await git.cloneRepo({
|
|
136
|
+
onProgress: options?.onProgress,
|
|
137
|
+
folderName: options?.folderName,
|
|
138
|
+
url: options.url,
|
|
139
|
+
branch: options?.branch,
|
|
140
|
+
bundlePath: options.bundlePath,
|
|
141
|
+
});
|
|
142
|
+
if (clone.success) {
|
|
143
|
+
await git.setConfig();
|
|
144
|
+
if (clone.bundle) {
|
|
145
|
+
await setupExactBundlePath(clone.bundle);
|
|
146
|
+
options?.onCloneSuccess?.();
|
|
147
|
+
if (options?.restartAfterInstall) {
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
resetApp();
|
|
150
|
+
}, 300);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
options?.onCloneFailed?.(clone.msg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
options?.onCloneFailed?.(e.toString());
|
|
159
|
+
} finally {
|
|
160
|
+
options?.onFinishProgress?.();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
115
163
|
export default {
|
|
116
164
|
setupBundlePath,
|
|
165
|
+
setupExactBundlePath,
|
|
117
166
|
removeUpdate: removeBundle,
|
|
118
167
|
downloadBundleUri,
|
|
119
168
|
resetApp,
|
|
120
169
|
getCurrentVersion: getVersionAsNumber,
|
|
121
170
|
setCurrentVersion,
|
|
171
|
+
git: {
|
|
172
|
+
checkForGitUpdate,
|
|
173
|
+
...git,
|
|
174
|
+
removeGitUpdate: (folder?: string) => {
|
|
175
|
+
RNhotupdate.setExactBundlePath('');
|
|
176
|
+
git.removeGitUpdate(folder);
|
|
177
|
+
},
|
|
178
|
+
},
|
|
122
179
|
};
|
package/src/type.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export interface UpdateOption {
|
|
2
|
+
headers?: object;
|
|
3
|
+
progress?(received: string, total: string): void;
|
|
4
|
+
updateSuccess?(): void;
|
|
5
|
+
updateFail?(message?: string): void;
|
|
6
|
+
restartAfterInstall?: boolean;
|
|
7
|
+
extensionBundle?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for updating a Git repository.
|
|
12
|
+
*/
|
|
13
|
+
export interface UpdateGitOption {
|
|
14
|
+
/**
|
|
15
|
+
* The URL of the Git repository to check update.
|
|
16
|
+
*/
|
|
17
|
+
url: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional callback to monitor the progress of the update.
|
|
21
|
+
* @param received - The number of bytes received so far.
|
|
22
|
+
* @param total - The total number of bytes to be received.
|
|
23
|
+
*/
|
|
24
|
+
onProgress?(received: number, total: number): void;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Optional branch name to update or switch to.
|
|
28
|
+
* If not specified, the default branch will be main.
|
|
29
|
+
*/
|
|
30
|
+
branch?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Optional name of the folder where the repository will be cloned or updated.
|
|
34
|
+
* If not specified, a default folder name will be git_hot_update.
|
|
35
|
+
*/
|
|
36
|
+
folderName?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Optional callback when pull success, should handle for case update.
|
|
39
|
+
*/
|
|
40
|
+
onPullSuccess?(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Optional callback when pull failed.
|
|
43
|
+
*/
|
|
44
|
+
onPullFailed?(msg: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Optional callback when clone success, handle it in the first time clone.
|
|
47
|
+
*/
|
|
48
|
+
onCloneSuccess?(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Optional callback when clone failed.
|
|
51
|
+
*/
|
|
52
|
+
onCloneFailed?(msg: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* The bundle path of the Git repository, it should place at root.
|
|
55
|
+
* Eg: the folder name is git_hot_update, bundle file place at git_hot_update/output/main.jsbundle, so bundlePath should be: "output/main.jsbundle".
|
|
56
|
+
*/
|
|
57
|
+
bundlePath: string;
|
|
58
|
+
/**
|
|
59
|
+
* Optional restart app after clone / pull success for apply the new bundle.
|
|
60
|
+
*/
|
|
61
|
+
restartAfterInstall?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Optional when all process success, use for set loading false.
|
|
64
|
+
*/
|
|
65
|
+
onFinishProgress?(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface CloneOption {
|
|
69
|
+
url: string;
|
|
70
|
+
folderName?: string;
|
|
71
|
+
onProgress?(received: number, total: number): void;
|
|
72
|
+
branch?: string;
|
|
73
|
+
bundlePath: string;
|
|
74
|
+
userName?: string;
|
|
75
|
+
email?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface PullOption {
|
|
79
|
+
folderName?: string;
|
|
80
|
+
onProgress?(received: number, total: number): void;
|
|
81
|
+
branch: string;
|
|
82
|
+
}
|