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 CHANGED
@@ -9,12 +9,13 @@
9
9
  ### 优势
10
10
 
11
11
  1. 基于阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。
12
- 2. 基于 bsdiff/hdiff 算法创建的**超小更新包**,通常版本迭代后在 1-10KB 之间(其他全量热更新服务所需流量通常在 1-10MB 级别)。
13
- 3. 跨越多个版本进行更新时,只需要下载**一个更新包**,不需要逐版本依次更新。
14
- 4. 命令行工具&网页双端管理,版本发布过程简单便捷,完全可以集成 CI。
15
- 5. 支持崩溃回滚,安全可靠。
16
- 6. meta 信息及开放 API,提供更高扩展性。
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
 
@@ -1,2 +1,3 @@
1
1
  -keepnames class cn.reactnative.modules.update.DownloadTask { *; }
2
+ -keepnames class cn.reactnative.modules.update.UpdateModule { *; }
2
3
  -keepnames class com.facebook.react.ReactInstanceManager { *; }
@@ -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
@@ -1 +1 @@
1
- 1654072539
1
+ 1574665292
@@ -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 = noop;
17
+ export const simpleUpdate = (app) => app;
18
+ export const onEvents = noop;
@@ -1,8 +1,7 @@
1
1
  import {
2
- tryBackupEndpoints,
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
- let Pushy = NativeModules.Pushy;
26
+ export const PushyModule = NativeModules.Pushy;
20
27
 
21
- if (!Pushy) {
28
+ if (!PushyModule) {
22
29
  throw new Error('react-native-update模块无法加载,请对照安装文档检查配置。');
23
30
  }
31
+ const PushyConstants = PushyModule;
24
32
 
25
- export const downloadRootDir = Pushy.downloadRootDir;
26
- export const packageVersion = Pushy.packageVersion;
27
- export const currentVersion = Pushy.currentVersion;
28
- export const isFirstTime = Pushy.isFirstTime;
29
- const rolledBackVersion = Pushy.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 = Pushy.buildTime;
33
- let blockUpdate = Pushy.blockUpdate;
34
- let uuid = Pushy.uuid;
40
+ export const buildTime = PushyConstants.buildTime;
41
+ let blockUpdate = PushyConstants.blockUpdate;
42
+ let uuid = PushyConstants.uuid;
35
43
 
36
- if (Platform.OS === 'android' && !Pushy.isUsingBundleUrl) {
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
- Pushy.setLocalHashInfo(hash, JSON.stringify(info));
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 Pushy.getLocalHashInfo(hash));
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(Pushy);
66
+ const eventEmitter = new NativeEventEmitter(PushyModule);
55
67
 
56
68
  if (!uuid) {
57
69
  uuid = require('nanoid/non-secure').nanoid();
58
- Pushy.setUuid(uuid);
70
+ PushyModule.setUuid(uuid);
59
71
  }
60
72
 
61
- function logger(text) {
62
- console.log(`Pushy: ${text}`);
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(hash, type) {
66
- logger(type);
67
- fetch(getReportUrl(), {
68
- method: 'POST',
69
- headers: {
70
- Accept: 'application/json',
71
- 'Content-Type': 'application/json',
72
- },
73
- body: JSON.stringify({
74
- hash,
75
- type,
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
- }).catch((_e) => {});
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
- function assertRelease() {
97
- if (__DEV__) {
98
- throw new Error('react-native-update 只能在 RELEASE 版本中运行.');
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
- if (checkingThrottling) {
106
- logger('repeated checking, ignored');
107
- return;
125
+ const now = Date.now();
126
+ if (lastResult && lastChecking && now - lastChecking < 1000 * 60) {
127
+ // logger('repeated checking, ignored');
128
+ return lastResult;
108
129
  }
109
- checkingThrottling = true;
110
- setTimeout(() => {
111
- checkingThrottling = false;
112
- }, 3000);
130
+ lastChecking = now;
113
131
  if (blockUpdate && blockUpdate.until > Date.now() / 1000) {
114
- throw new Error(
115
- `热更新已暂停,原因:${blockUpdate.reason}。请在"${new Date(
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
- logger('checking update');
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
- if (isRetry) {
141
- throw new Error('无法连接更新服务器,请检查网络连接后重试');
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
- const result = await resp.json();
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
- throw new Error(result.message);
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(op) {
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
- Pushy.setBlockUpdate(blockUpdate);
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(options, eventListeners) {
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 Pushy.downloadPatchFromPpk({
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 Pushy.downloadPatchFromPackage({
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 Pushy.downloadFullUpdate({
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, 'error');
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
- Pushy.reloadUpdate({ hash });
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
- Pushy.setNeedUpdate({ hash });
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
- Pushy.markSuccess();
297
- report(currentVersion, 'success');
344
+ PushyModule.markSuccess();
345
+ report({ type: 'markSuccess' });
298
346
  }
299
347
 
300
- export async function downloadAndInstallApk({ url, onDownloadProgress }) {
301
- logger('downloadAndInstallApk');
302
- if (Platform.OS === 'android' && Platform.Version <= 23) {
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
- console.warn(err);
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 Pushy.downloadAndInstallApk({
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, { Component } from 'react';
2
- import { Platform, Alert, Linking, AppState } from 'react-native';
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(WrappedComponent, options = {}) {
16
- const { appKey } = options;
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 Component {
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
@@ -0,0 +1,9 @@
1
+ export function logger(...args: any[]) {
2
+ console.log('Pushy: ', ...args);
3
+ }
4
+
5
+ export function assertRelease() {
6
+ if (__DEV__) {
7
+ throw new Error('react-native-update 只能在 RELEASE 版本中运行.');
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "8.1.0",
3
+ "version": "8.3.0",
4
4
  "description": "react-native hot update",
5
- "main": "lib/index.js",
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.27.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