react-native-update 8.1.0 → 8.3.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/README.md +7 -6
- package/android/proguard.pro +1 -0
- package/android/src/main/AndroidManifest.xml +0 -3
- package/ios/pushy_build_time.txt +1 -1
- package/lib/endpoint.ts +53 -0
- package/lib/index.web.js +2 -1
- package/lib/{main.js → main.ts} +162 -103
- package/lib/{simpleUpdate.js → simpleUpdate.tsx} +21 -6
- package/lib/type.ts +71 -0
- package/lib/utils.ts +9 -0
- package/package.json +9 -3
- package/lib/endpoint.js +0 -93
- package/lib/index.d.ts +0 -94
- /package/lib/{index.js → index.ts} +0 -0
package/README.md
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
### 优势
|
|
10
10
|
|
|
11
11
|
1. 基于阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。
|
|
12
|
-
2. 基于 bsdiff/hdiff
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
15
|
-
5.
|
|
16
|
-
6.
|
|
17
|
-
7.
|
|
12
|
+
2. 基于 bsdiff/hdiff 算法创建的**超小更新包**,通常版本迭代后在几十 KB 级别(其他全量热更新服务所需流量通常在几十 MB 级别)。
|
|
13
|
+
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。(暂不支持新架构,会待其相对稳定后跟进)
|
|
14
|
+
4. 跨越多个版本进行更新时,只需要下载**一个更新包**,不需要逐版本依次更新。
|
|
15
|
+
5. 命令行工具 & 网页双端管理,版本发布过程简单便捷,完全可以集成 CI。
|
|
16
|
+
6. 支持崩溃回滚,安全可靠。
|
|
17
|
+
7. meta 信息及开放 API,提供更高扩展性。
|
|
18
|
+
8. 提供付费的专人技术支持。
|
|
18
19
|
|
|
19
20
|
### 本地开发
|
|
20
21
|
|
package/android/proguard.pro
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
3
|
package="cn.reactnative.modules.update">
|
|
4
|
-
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
5
|
-
|
|
6
|
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
7
4
|
<application>
|
|
8
5
|
<meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" />
|
|
9
6
|
<provider
|
package/ios/pushy_build_time.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1574665292
|
package/lib/endpoint.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { logger } from './utils';
|
|
2
|
+
|
|
3
|
+
let currentEndpoint = 'https://update.react-native.cn/api';
|
|
4
|
+
let backupEndpoints: string[] = ['https://update.reactnative.cn/api'];
|
|
5
|
+
let backupEndpointsQueryUrl: string | null = null;
|
|
6
|
+
|
|
7
|
+
export async function updateBackupEndpoints() {
|
|
8
|
+
if (backupEndpointsQueryUrl) {
|
|
9
|
+
try {
|
|
10
|
+
const resp = await fetch(backupEndpointsQueryUrl);
|
|
11
|
+
const remoteEndpoints = await resp.json();
|
|
12
|
+
if (Array.isArray(remoteEndpoints)) {
|
|
13
|
+
backupEndpoints = Array.from(
|
|
14
|
+
new Set([...backupEndpoints, ...remoteEndpoints]),
|
|
15
|
+
);
|
|
16
|
+
logger('fetch remote endpoints:', remoteEndpoints);
|
|
17
|
+
logger('merged backup endpoints:', backupEndpoints);
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {
|
|
20
|
+
logger('fetch remote endpoints failed');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return backupEndpoints;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getCheckUrl(APPKEY, endpoint = currentEndpoint) {
|
|
27
|
+
return `${endpoint}/checkUpdate/${APPKEY}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} main - The main api endpoint
|
|
32
|
+
* @param {string[]} [backups] - The back up endpoints.
|
|
33
|
+
* @param {string} [backupQueryUrl] - An url that return a json file containing an array of endpoint.
|
|
34
|
+
* like: ["https://backup.api/1", "https://backup.api/2"]
|
|
35
|
+
*/
|
|
36
|
+
export function setCustomEndpoints({
|
|
37
|
+
main,
|
|
38
|
+
backups,
|
|
39
|
+
backupQueryUrl,
|
|
40
|
+
}: {
|
|
41
|
+
main: string;
|
|
42
|
+
backups?: string[];
|
|
43
|
+
backupQueryUrl?: string;
|
|
44
|
+
}) {
|
|
45
|
+
currentEndpoint = main;
|
|
46
|
+
backupEndpointsQueryUrl = null;
|
|
47
|
+
if (Array.isArray(backups) && backups.length > 0) {
|
|
48
|
+
backupEndpoints = backups;
|
|
49
|
+
}
|
|
50
|
+
if (typeof backupQueryUrl === 'string') {
|
|
51
|
+
backupEndpointsQueryUrl = backupQueryUrl;
|
|
52
|
+
}
|
|
53
|
+
}
|
package/lib/index.web.js
CHANGED
|
@@ -14,4 +14,5 @@ export const markSuccess = noop;
|
|
|
14
14
|
export const downloadAndInstallApk = noop;
|
|
15
15
|
export const setCustomEndpoints = noop;
|
|
16
16
|
export const getCurrentVersionInfo = noop;
|
|
17
|
-
export const simpleUpdate =
|
|
17
|
+
export const simpleUpdate = (app) => app;
|
|
18
|
+
export const onEvents = noop;
|
package/lib/{main.js → main.ts}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
updateBackupEndpoints,
|
|
3
3
|
getCheckUrl,
|
|
4
4
|
setCustomEndpoints,
|
|
5
|
-
getReportUrl,
|
|
6
5
|
} from './endpoint';
|
|
7
6
|
import {
|
|
8
7
|
NativeEventEmitter,
|
|
@@ -10,82 +9,107 @@ import {
|
|
|
10
9
|
Platform,
|
|
11
10
|
PermissionsAndroid,
|
|
12
11
|
} from 'react-native';
|
|
12
|
+
import {
|
|
13
|
+
CheckResult,
|
|
14
|
+
EventType,
|
|
15
|
+
ProgressData,
|
|
16
|
+
UpdateAvailableResult,
|
|
17
|
+
UpdateEventsListener,
|
|
18
|
+
} from './type';
|
|
19
|
+
import { assertRelease, logger } from './utils';
|
|
13
20
|
export { setCustomEndpoints };
|
|
14
21
|
const {
|
|
15
22
|
version: v,
|
|
16
23
|
} = require('react-native/Libraries/Core/ReactNativeVersion');
|
|
17
24
|
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
export const PushyModule = NativeModules.Pushy;
|
|
20
27
|
|
|
21
|
-
if (!
|
|
28
|
+
if (!PushyModule) {
|
|
22
29
|
throw new Error('react-native-update模块无法加载,请对照安装文档检查配置。');
|
|
23
30
|
}
|
|
31
|
+
const PushyConstants = PushyModule;
|
|
24
32
|
|
|
25
|
-
export const downloadRootDir =
|
|
26
|
-
export const packageVersion =
|
|
27
|
-
export const currentVersion =
|
|
28
|
-
export const isFirstTime =
|
|
29
|
-
const rolledBackVersion =
|
|
33
|
+
export const downloadRootDir = PushyConstants.downloadRootDir;
|
|
34
|
+
export const packageVersion = PushyConstants.packageVersion;
|
|
35
|
+
export const currentVersion = PushyConstants.currentVersion;
|
|
36
|
+
export const isFirstTime = PushyConstants.isFirstTime;
|
|
37
|
+
const rolledBackVersion = PushyConstants.rolledBackVersion;
|
|
30
38
|
export const isRolledBack = typeof rolledBackVersion === 'string';
|
|
31
39
|
|
|
32
|
-
export const buildTime =
|
|
33
|
-
let blockUpdate =
|
|
34
|
-
let uuid =
|
|
40
|
+
export const buildTime = PushyConstants.buildTime;
|
|
41
|
+
let blockUpdate = PushyConstants.blockUpdate;
|
|
42
|
+
let uuid = PushyConstants.uuid;
|
|
35
43
|
|
|
36
|
-
if (Platform.OS === 'android' && !
|
|
44
|
+
if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
|
|
37
45
|
throw new Error(
|
|
38
46
|
'react-native-update模块无法加载,请对照文档检查Bundle URL的配置',
|
|
39
47
|
);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
function setLocalHashInfo(hash, info) {
|
|
43
|
-
|
|
50
|
+
function setLocalHashInfo(hash: string, info: Record<string, any>) {
|
|
51
|
+
PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
async function getLocalHashInfo(hash) {
|
|
47
|
-
return JSON.parse(await
|
|
54
|
+
async function getLocalHashInfo(hash: string) {
|
|
55
|
+
return JSON.parse(await PushyModule.getLocalHashInfo(hash));
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
export async function getCurrentVersionInfo() {
|
|
58
|
+
export async function getCurrentVersionInfo(): Promise<{
|
|
59
|
+
name?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
metaInfo?: string;
|
|
62
|
+
}> {
|
|
51
63
|
return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
const eventEmitter = new NativeEventEmitter(
|
|
66
|
+
const eventEmitter = new NativeEventEmitter(PushyModule);
|
|
55
67
|
|
|
56
68
|
if (!uuid) {
|
|
57
69
|
uuid = require('nanoid/non-secure').nanoid();
|
|
58
|
-
|
|
70
|
+
PushyModule.setUuid(uuid);
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
const noop = () => {};
|
|
74
|
+
let reporter: UpdateEventsListener = noop;
|
|
75
|
+
|
|
76
|
+
export function onEvents(customReporter: UpdateEventsListener) {
|
|
77
|
+
reporter = customReporter;
|
|
78
|
+
if (isRolledBack) {
|
|
79
|
+
report({
|
|
80
|
+
type: 'rollback',
|
|
81
|
+
data: {
|
|
82
|
+
rolledBackVersion,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
function report(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
function report({
|
|
89
|
+
type,
|
|
90
|
+
message = '',
|
|
91
|
+
data = {},
|
|
92
|
+
}: {
|
|
93
|
+
type: EventType;
|
|
94
|
+
message?: string;
|
|
95
|
+
data?: Record<string, string | number>;
|
|
96
|
+
}) {
|
|
97
|
+
logger(type + ' ' + message);
|
|
98
|
+
reporter({
|
|
99
|
+
type,
|
|
100
|
+
data: {
|
|
101
|
+
currentVersion,
|
|
76
102
|
cInfo,
|
|
77
103
|
packageVersion,
|
|
78
104
|
buildTime,
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
message,
|
|
106
|
+
...data,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
81
109
|
}
|
|
82
110
|
|
|
83
111
|
logger('uuid: ' + uuid);
|
|
84
112
|
|
|
85
|
-
if (isRolledBack) {
|
|
86
|
-
report(rolledBackVersion, 'rollback');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
113
|
export const cInfo = {
|
|
90
114
|
pushy: require('../package.json').version,
|
|
91
115
|
rn: RNVersion,
|
|
@@ -93,67 +117,86 @@ export const cInfo = {
|
|
|
93
117
|
uuid,
|
|
94
118
|
};
|
|
95
119
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let checkingThrottling = false;
|
|
103
|
-
export async function checkUpdate(APPKEY, isRetry) {
|
|
120
|
+
let lastChecking;
|
|
121
|
+
const empty = {};
|
|
122
|
+
let lastResult: CheckResult;
|
|
123
|
+
export async function checkUpdate(APPKEY: string) {
|
|
104
124
|
assertRelease();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (lastResult && lastChecking && now - lastChecking < 1000 * 60) {
|
|
127
|
+
// logger('repeated checking, ignored');
|
|
128
|
+
return lastResult;
|
|
108
129
|
}
|
|
109
|
-
|
|
110
|
-
setTimeout(() => {
|
|
111
|
-
checkingThrottling = false;
|
|
112
|
-
}, 3000);
|
|
130
|
+
lastChecking = now;
|
|
113
131
|
if (blockUpdate && blockUpdate.until > Date.now() / 1000) {
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
report({
|
|
133
|
+
type: 'errorChecking',
|
|
134
|
+
message: `热更新已暂停,原因:${blockUpdate.reason}。请在"${new Date(
|
|
116
135
|
blockUpdate.until * 1000,
|
|
117
136
|
).toLocaleString()}"之后重试。`,
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
if (typeof APPKEY !== 'string') {
|
|
121
|
-
throw new Error('未检查到合法的APPKEY,请查看update.json文件是否正确生成');
|
|
137
|
+
});
|
|
138
|
+
return lastResult || empty;
|
|
122
139
|
}
|
|
123
|
-
|
|
140
|
+
report({ type: 'checking' });
|
|
141
|
+
const fetchPayload = {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
Accept: 'application/json',
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
packageVersion,
|
|
149
|
+
hash: currentVersion,
|
|
150
|
+
buildTime,
|
|
151
|
+
cInfo,
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
124
154
|
let resp;
|
|
125
155
|
try {
|
|
126
|
-
resp = await fetch(getCheckUrl(APPKEY),
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: {
|
|
129
|
-
Accept: 'application/json',
|
|
130
|
-
'Content-Type': 'application/json',
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
packageVersion,
|
|
134
|
-
hash: currentVersion,
|
|
135
|
-
buildTime,
|
|
136
|
-
cInfo,
|
|
137
|
-
}),
|
|
138
|
-
});
|
|
156
|
+
resp = await fetch(getCheckUrl(APPKEY), fetchPayload);
|
|
139
157
|
} catch (e) {
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
report({
|
|
159
|
+
type: 'errorChecking',
|
|
160
|
+
message: '无法连接主更新服务器,尝试备用节点',
|
|
161
|
+
});
|
|
162
|
+
const backupEndpoints = await updateBackupEndpoints();
|
|
163
|
+
if (backupEndpoints) {
|
|
164
|
+
try {
|
|
165
|
+
resp = await Promise.race(
|
|
166
|
+
backupEndpoints.map((endpoint) =>
|
|
167
|
+
fetch(getCheckUrl(APPKEY, endpoint), fetchPayload),
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
} catch {}
|
|
142
171
|
}
|
|
143
|
-
await tryBackupEndpoints();
|
|
144
|
-
return checkUpdate(APPKEY, true);
|
|
145
172
|
}
|
|
146
|
-
|
|
173
|
+
if (!resp) {
|
|
174
|
+
report({
|
|
175
|
+
type: 'errorChecking',
|
|
176
|
+
message: '无法连接更新服务器,请检查网络连接后重试',
|
|
177
|
+
});
|
|
178
|
+
return lastResult || empty;
|
|
179
|
+
}
|
|
180
|
+
const result: CheckResult = await resp.json();
|
|
181
|
+
|
|
182
|
+
lastResult = result;
|
|
183
|
+
// @ts-ignore
|
|
147
184
|
checkOperation(result.op);
|
|
148
185
|
|
|
149
186
|
if (resp.status !== 200) {
|
|
150
|
-
|
|
187
|
+
report({
|
|
188
|
+
type: 'errorChecking',
|
|
189
|
+
//@ts-ignore
|
|
190
|
+
message: result.message,
|
|
191
|
+
});
|
|
151
192
|
}
|
|
152
193
|
|
|
153
194
|
return result;
|
|
154
195
|
}
|
|
155
196
|
|
|
156
|
-
function checkOperation(
|
|
197
|
+
function checkOperation(
|
|
198
|
+
op: { type: string; reason: string; duration: number }[],
|
|
199
|
+
) {
|
|
157
200
|
if (!Array.isArray(op)) {
|
|
158
201
|
return;
|
|
159
202
|
}
|
|
@@ -163,14 +206,19 @@ function checkOperation(op) {
|
|
|
163
206
|
reason: action.reason,
|
|
164
207
|
until: Math.round((Date.now() + action.duration) / 1000),
|
|
165
208
|
};
|
|
166
|
-
|
|
209
|
+
PushyModule.setBlockUpdate(blockUpdate);
|
|
167
210
|
}
|
|
168
211
|
});
|
|
169
212
|
}
|
|
170
213
|
|
|
171
214
|
let downloadingThrottling = false;
|
|
172
|
-
let downloadedHash;
|
|
173
|
-
export async function downloadUpdate(
|
|
215
|
+
let downloadedHash: string;
|
|
216
|
+
export async function downloadUpdate(
|
|
217
|
+
options: UpdateAvailableResult,
|
|
218
|
+
eventListeners?: {
|
|
219
|
+
onDownloadProgress?: (data: ProgressData) => void;
|
|
220
|
+
},
|
|
221
|
+
) {
|
|
174
222
|
assertRelease();
|
|
175
223
|
if (!options.update) {
|
|
176
224
|
return;
|
|
@@ -206,10 +254,11 @@ export async function downloadUpdate(options, eventListeners) {
|
|
|
206
254
|
}
|
|
207
255
|
}
|
|
208
256
|
let succeeded = false;
|
|
257
|
+
report({ type: 'downloading' });
|
|
209
258
|
if (options.diffUrl) {
|
|
210
259
|
logger('downloading diff');
|
|
211
260
|
try {
|
|
212
|
-
await
|
|
261
|
+
await PushyModule.downloadPatchFromPpk({
|
|
213
262
|
updateUrl: options.diffUrl,
|
|
214
263
|
hash: options.hash,
|
|
215
264
|
originHash: currentVersion,
|
|
@@ -222,7 +271,7 @@ export async function downloadUpdate(options, eventListeners) {
|
|
|
222
271
|
if (!succeeded && options.pdiffUrl) {
|
|
223
272
|
logger('downloading pdiff');
|
|
224
273
|
try {
|
|
225
|
-
await
|
|
274
|
+
await PushyModule.downloadPatchFromPackage({
|
|
226
275
|
updateUrl: options.pdiffUrl,
|
|
227
276
|
hash: options.hash,
|
|
228
277
|
});
|
|
@@ -234,7 +283,7 @@ export async function downloadUpdate(options, eventListeners) {
|
|
|
234
283
|
if (!succeeded && options.updateUrl) {
|
|
235
284
|
logger('downloading full patch');
|
|
236
285
|
try {
|
|
237
|
-
await
|
|
286
|
+
await PushyModule.downloadFullUpdate({
|
|
238
287
|
updateUrl: options.updateUrl,
|
|
239
288
|
hash: options.hash,
|
|
240
289
|
});
|
|
@@ -245,8 +294,7 @@ export async function downloadUpdate(options, eventListeners) {
|
|
|
245
294
|
}
|
|
246
295
|
progressHandler && progressHandler.remove();
|
|
247
296
|
if (!succeeded) {
|
|
248
|
-
report(options.hash
|
|
249
|
-
throw new Error('all update attempts failed');
|
|
297
|
+
return report({ type: 'errorUpdate', data: { newVersion: options.hash } });
|
|
250
298
|
}
|
|
251
299
|
setLocalHashInfo(options.hash, {
|
|
252
300
|
name: options.name,
|
|
@@ -257,7 +305,7 @@ export async function downloadUpdate(options, eventListeners) {
|
|
|
257
305
|
return options.hash;
|
|
258
306
|
}
|
|
259
307
|
|
|
260
|
-
function assertHash(hash) {
|
|
308
|
+
function assertHash(hash: string) {
|
|
261
309
|
if (!downloadedHash) {
|
|
262
310
|
logger(`no downloaded hash`);
|
|
263
311
|
return;
|
|
@@ -269,19 +317,19 @@ function assertHash(hash) {
|
|
|
269
317
|
return true;
|
|
270
318
|
}
|
|
271
319
|
|
|
272
|
-
export function switchVersion(hash) {
|
|
320
|
+
export function switchVersion(hash: string) {
|
|
273
321
|
assertRelease();
|
|
274
322
|
if (assertHash(hash)) {
|
|
275
323
|
logger('switchVersion: ' + hash);
|
|
276
|
-
|
|
324
|
+
PushyModule.reloadUpdate({ hash });
|
|
277
325
|
}
|
|
278
326
|
}
|
|
279
327
|
|
|
280
|
-
export function switchVersionLater(hash) {
|
|
328
|
+
export function switchVersionLater(hash: string) {
|
|
281
329
|
assertRelease();
|
|
282
330
|
if (assertHash(hash)) {
|
|
283
331
|
logger('switchVersionLater: ' + hash);
|
|
284
|
-
|
|
332
|
+
PushyModule.setNeedUpdate({ hash });
|
|
285
333
|
}
|
|
286
334
|
}
|
|
287
335
|
|
|
@@ -293,22 +341,31 @@ export function markSuccess() {
|
|
|
293
341
|
return;
|
|
294
342
|
}
|
|
295
343
|
marked = true;
|
|
296
|
-
|
|
297
|
-
report(
|
|
344
|
+
PushyModule.markSuccess();
|
|
345
|
+
report({ type: 'markSuccess' });
|
|
298
346
|
}
|
|
299
347
|
|
|
300
|
-
export async function downloadAndInstallApk({
|
|
301
|
-
|
|
302
|
-
|
|
348
|
+
export async function downloadAndInstallApk({
|
|
349
|
+
url,
|
|
350
|
+
onDownloadProgress,
|
|
351
|
+
}: {
|
|
352
|
+
url: string;
|
|
353
|
+
onDownloadProgress?: (data: ProgressData) => void;
|
|
354
|
+
}) {
|
|
355
|
+
if (Platform.OS !== 'android') {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
report({ type: 'downloadingApk' });
|
|
359
|
+
if (Platform.Version <= 23) {
|
|
303
360
|
try {
|
|
304
361
|
const granted = await PermissionsAndroid.request(
|
|
305
362
|
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
|
306
363
|
);
|
|
307
364
|
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
308
|
-
return;
|
|
365
|
+
return report({ type: 'rejectStoragePermission' });
|
|
309
366
|
}
|
|
310
367
|
} catch (err) {
|
|
311
|
-
|
|
368
|
+
return report({ type: 'errorStoragePermission' });
|
|
312
369
|
}
|
|
313
370
|
}
|
|
314
371
|
let hash = Date.now().toString();
|
|
@@ -316,17 +373,19 @@ export async function downloadAndInstallApk({ url, onDownloadProgress }) {
|
|
|
316
373
|
if (onDownloadProgress) {
|
|
317
374
|
progressHandler = eventEmitter.addListener(
|
|
318
375
|
'RCTPushyDownloadProgress',
|
|
319
|
-
(progressData) => {
|
|
376
|
+
(progressData: ProgressData) => {
|
|
320
377
|
if (progressData.hash === hash) {
|
|
321
378
|
onDownloadProgress(progressData);
|
|
322
379
|
}
|
|
323
380
|
},
|
|
324
381
|
);
|
|
325
382
|
}
|
|
326
|
-
await
|
|
383
|
+
await PushyModule.downloadAndInstallApk({
|
|
327
384
|
url,
|
|
328
385
|
target: 'update.apk',
|
|
329
386
|
hash,
|
|
387
|
+
}).catch(() => {
|
|
388
|
+
report({ type: 'errowDownloadAndInstallApk' });
|
|
330
389
|
});
|
|
331
390
|
progressHandler && progressHandler.remove();
|
|
332
391
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
1
|
+
import React, { PureComponent, ComponentType } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
Alert,
|
|
5
|
+
Linking,
|
|
6
|
+
AppState,
|
|
7
|
+
NativeEventSubscription,
|
|
8
|
+
} from 'react-native';
|
|
3
9
|
|
|
4
10
|
import {
|
|
5
11
|
isFirstTime,
|
|
@@ -10,16 +16,25 @@ import {
|
|
|
10
16
|
switchVersionLater,
|
|
11
17
|
markSuccess,
|
|
12
18
|
downloadAndInstallApk,
|
|
19
|
+
onEvents,
|
|
13
20
|
} from './main';
|
|
21
|
+
import { UpdateEventsListener } from './type';
|
|
14
22
|
|
|
15
|
-
export function simpleUpdate(
|
|
16
|
-
|
|
23
|
+
export function simpleUpdate(
|
|
24
|
+
WrappedComponent: ComponentType,
|
|
25
|
+
options: { appKey?: string; onEvents?: UpdateEventsListener } = {},
|
|
26
|
+
) {
|
|
27
|
+
const { appKey, onEvents: eventListeners } = options;
|
|
17
28
|
if (!appKey) {
|
|
18
29
|
throw new Error('appKey is required for simpleUpdate()');
|
|
19
30
|
}
|
|
31
|
+
if (typeof eventListeners === 'function') {
|
|
32
|
+
onEvents(eventListeners);
|
|
33
|
+
}
|
|
20
34
|
return __DEV__
|
|
21
35
|
? WrappedComponent
|
|
22
|
-
: class AppUpdate extends
|
|
36
|
+
: class AppUpdate extends PureComponent {
|
|
37
|
+
stateListener: NativeEventSubscription;
|
|
23
38
|
componentDidMount() {
|
|
24
39
|
if (isRolledBack) {
|
|
25
40
|
Alert.alert('抱歉', '刚刚更新遭遇错误,已为您恢复到更新前版本');
|
|
@@ -70,7 +85,7 @@ export function simpleUpdate(WrappedComponent, options = {}) {
|
|
|
70
85
|
checkUpdate = async () => {
|
|
71
86
|
let info;
|
|
72
87
|
try {
|
|
73
|
-
info = await checkUpdate(appKey);
|
|
88
|
+
info = await checkUpdate(appKey!);
|
|
74
89
|
} catch (err) {
|
|
75
90
|
Alert.alert('更新检查失败', err.message);
|
|
76
91
|
return;
|
package/lib/type.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface ExpiredResult {
|
|
2
|
+
upToDate?: false;
|
|
3
|
+
expired: true;
|
|
4
|
+
downloadUrl: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UpTodateResult {
|
|
8
|
+
expired?: false;
|
|
9
|
+
upToDate: true;
|
|
10
|
+
paused?: 'app' | 'package';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UpdateAvailableResult {
|
|
14
|
+
expired?: false;
|
|
15
|
+
upToDate?: false;
|
|
16
|
+
update: true;
|
|
17
|
+
name: string; // version name
|
|
18
|
+
hash: string;
|
|
19
|
+
description: string;
|
|
20
|
+
metaInfo: string;
|
|
21
|
+
pdiffUrl: string;
|
|
22
|
+
diffUrl?: string;
|
|
23
|
+
updateUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type CheckResult =
|
|
27
|
+
| ExpiredResult
|
|
28
|
+
| UpTodateResult
|
|
29
|
+
| UpdateAvailableResult
|
|
30
|
+
| {};
|
|
31
|
+
|
|
32
|
+
export interface ProgressData {
|
|
33
|
+
hash: string;
|
|
34
|
+
received: number;
|
|
35
|
+
total: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type EventType =
|
|
39
|
+
| 'rollback'
|
|
40
|
+
| 'errorChecking'
|
|
41
|
+
| 'checking'
|
|
42
|
+
| 'downloading'
|
|
43
|
+
| 'errorUpdate'
|
|
44
|
+
| 'markSuccess'
|
|
45
|
+
| 'downloadingApk'
|
|
46
|
+
| 'rejectStoragePermission'
|
|
47
|
+
| 'errorStoragePermission'
|
|
48
|
+
| 'errowDownloadAndInstallApk';
|
|
49
|
+
|
|
50
|
+
export interface EventData {
|
|
51
|
+
currentVersion: string;
|
|
52
|
+
cInfo: {
|
|
53
|
+
pushy: string;
|
|
54
|
+
rn: string;
|
|
55
|
+
os: string;
|
|
56
|
+
uuid: string;
|
|
57
|
+
};
|
|
58
|
+
packageVersion: string;
|
|
59
|
+
buildTime: number;
|
|
60
|
+
message?: string;
|
|
61
|
+
rolledBackVersion?: string;
|
|
62
|
+
newVersion?: string;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
export type UpdateEventsListener = ({
|
|
66
|
+
type,
|
|
67
|
+
data,
|
|
68
|
+
}: {
|
|
69
|
+
type: EventType;
|
|
70
|
+
data: EventData;
|
|
71
|
+
}) => void;
|
package/lib/utils.ts
ADDED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
|
-
"main": "lib/index.
|
|
5
|
+
"main": "lib/index.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepublish": "yarn submodule",
|
|
8
8
|
"submodule": "git submodule update --init --recursive",
|
|
@@ -25,10 +25,16 @@
|
|
|
25
25
|
"url": "https://github.com/reactnativecn/react-native-pushy/issues"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"react-native": ">=0.
|
|
28
|
+
"react-native": ">=0.57.0"
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/reactnativecn/react-native-pushy#readme",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"nanoid": "^3.3.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.8.9",
|
|
36
|
+
"@types/react": "^18.2.33",
|
|
37
|
+
"react-native": "^0.72.6",
|
|
38
|
+
"typescript": "^5.2.2"
|
|
33
39
|
}
|
|
34
40
|
}
|
package/lib/endpoint.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
let currentEndpoint = 'https://update.react-native.cn/api';
|
|
2
|
-
|
|
3
|
-
function ping(url, rejectImmediate) {
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
const xhr = new XMLHttpRequest();
|
|
6
|
-
xhr.onreadystatechange = (e) => {
|
|
7
|
-
if (xhr.readyState !== 4) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
if (xhr.status === 200) {
|
|
11
|
-
resolve(url);
|
|
12
|
-
} else {
|
|
13
|
-
rejectImmediate ? reject() : setTimeout(reject, 5000);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
xhr.open('HEAD', url);
|
|
17
|
-
xhr.send();
|
|
18
|
-
xhr.timeout = 5000;
|
|
19
|
-
xhr.ontimeout = reject;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function logger(...args) {
|
|
24
|
-
// console.warn('pushy', ...args);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let backupEndpoints = [];
|
|
28
|
-
let backupEndpointsQueryUrl =
|
|
29
|
-
'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-pushy@master/endpoints.json';
|
|
30
|
-
|
|
31
|
-
export async function tryBackupEndpoints() {
|
|
32
|
-
if (!backupEndpoints.length && !backupEndpointsQueryUrl) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
await ping(getStatusUrl(), true);
|
|
37
|
-
logger('current endpoint ok');
|
|
38
|
-
return;
|
|
39
|
-
} catch (e) {
|
|
40
|
-
logger('current endpoint failed');
|
|
41
|
-
}
|
|
42
|
-
if (!backupEndpoints.length && backupEndpointsQueryUrl) {
|
|
43
|
-
try {
|
|
44
|
-
const resp = await fetch(backupEndpointsQueryUrl);
|
|
45
|
-
backupEndpoints = await resp.json();
|
|
46
|
-
logger('get remote endpoints:', backupEndpoints);
|
|
47
|
-
} catch (e) {
|
|
48
|
-
logger('get remote endpoints failed');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
await pickFatestAvailableEndpoint();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function pickFatestAvailableEndpoint(endpoints = backupEndpoints) {
|
|
56
|
-
const fastestEndpoint = await Promise.race(
|
|
57
|
-
endpoints.map(pingAndReturnEndpoint),
|
|
58
|
-
);
|
|
59
|
-
if (typeof fastestEndpoint === 'string') {
|
|
60
|
-
logger(`pick endpoint: ${fastestEndpoint}`);
|
|
61
|
-
currentEndpoint = fastestEndpoint;
|
|
62
|
-
} else {
|
|
63
|
-
logger('all remote endpoints failed');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function pingAndReturnEndpoint(endpoint = currentEndpoint) {
|
|
68
|
-
return ping(getStatusUrl(endpoint)).then(() => endpoint);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getStatusUrl(endpoint = currentEndpoint) {
|
|
72
|
-
return `${endpoint}/status`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function getCheckUrl(APPKEY, endpoint = currentEndpoint) {
|
|
76
|
-
return `${endpoint}/checkUpdate/${APPKEY}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function getReportUrl(endpoint = currentEndpoint) {
|
|
80
|
-
return `${endpoint}/report`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function setCustomEndpoints({ main, backups, backupQueryUrl }) {
|
|
84
|
-
currentEndpoint = main;
|
|
85
|
-
backupEndpointsQueryUrl = null;
|
|
86
|
-
if (Array.isArray(backups) && backups.length > 0) {
|
|
87
|
-
backupEndpoints = backups;
|
|
88
|
-
pickFatestAvailableEndpoint();
|
|
89
|
-
}
|
|
90
|
-
if (typeof backupQueryUrl === 'string') {
|
|
91
|
-
backupEndpointsQueryUrl = backupQueryUrl;
|
|
92
|
-
}
|
|
93
|
-
}
|
package/lib/index.d.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export const downloadRootDir: string;
|
|
2
|
-
export const packageVersion: string;
|
|
3
|
-
export const currentVersion: string;
|
|
4
|
-
export const isFirstTime: boolean;
|
|
5
|
-
export const isRolledBack: boolean;
|
|
6
|
-
|
|
7
|
-
export interface ExpiredResult {
|
|
8
|
-
upToDate?: false;
|
|
9
|
-
expired: true;
|
|
10
|
-
downloadUrl: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface UpTodateResult {
|
|
14
|
-
expired?: false;
|
|
15
|
-
upToDate: true;
|
|
16
|
-
paused?: 'app' | 'package';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface UpdateAvailableResult {
|
|
20
|
-
expired?: false;
|
|
21
|
-
upToDate?: false;
|
|
22
|
-
update: true;
|
|
23
|
-
name: string; // version name
|
|
24
|
-
hash: string;
|
|
25
|
-
description: string;
|
|
26
|
-
metaInfo: string;
|
|
27
|
-
pdiffUrl: string;
|
|
28
|
-
diffUrl?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type CheckResult =
|
|
32
|
-
| ExpiredResult
|
|
33
|
-
| UpTodateResult
|
|
34
|
-
| UpdateAvailableResult;
|
|
35
|
-
|
|
36
|
-
export function checkUpdate(appkey: string): Promise<CheckResult>;
|
|
37
|
-
|
|
38
|
-
export function downloadUpdate(
|
|
39
|
-
info: UpdateAvailableResult,
|
|
40
|
-
eventListeners?: {
|
|
41
|
-
onDownloadProgress?: (data: ProgressData) => void;
|
|
42
|
-
},
|
|
43
|
-
): Promise<undefined | string>;
|
|
44
|
-
|
|
45
|
-
export function switchVersion(hash: string): void;
|
|
46
|
-
|
|
47
|
-
export function switchVersionLater(hash: string): void;
|
|
48
|
-
|
|
49
|
-
export function markSuccess(): void;
|
|
50
|
-
|
|
51
|
-
export function downloadAndInstallApk({
|
|
52
|
-
url,
|
|
53
|
-
onDownloadProgress,
|
|
54
|
-
}: {
|
|
55
|
-
url: string;
|
|
56
|
-
onDownloadProgress?: (data: ProgressData) => void;
|
|
57
|
-
}): Promise<void>;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {string} main - The main api endpoint
|
|
61
|
-
* @param {string[]} [backups] - The back up endpoints.
|
|
62
|
-
* @param {string} [backupQueryUrl] - An url that return a json file containing an array of endpoint.
|
|
63
|
-
* like: ["https://backup.api/1", "https://backup.api/2"]
|
|
64
|
-
*/
|
|
65
|
-
export function setCustomEndpoints({
|
|
66
|
-
main,
|
|
67
|
-
backups,
|
|
68
|
-
backupQueryUrl,
|
|
69
|
-
}: {
|
|
70
|
-
main: string;
|
|
71
|
-
backups?: string[];
|
|
72
|
-
backupQueryUrl?: string;
|
|
73
|
-
}): void;
|
|
74
|
-
|
|
75
|
-
export function getCurrentVersionInfo(): Promise<{
|
|
76
|
-
name?: string;
|
|
77
|
-
description?: string;
|
|
78
|
-
metaInfo?: string;
|
|
79
|
-
}>;
|
|
80
|
-
|
|
81
|
-
interface ProgressData {
|
|
82
|
-
hash: string;
|
|
83
|
-
received: number;
|
|
84
|
-
total: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface SimpleUpdateOptions {
|
|
88
|
-
appKey: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function simpleUpdate(
|
|
92
|
-
wrappedComponent: any,
|
|
93
|
-
options: SimpleUpdateOptions,
|
|
94
|
-
): any;
|
|
File without changes
|