react-native-update 9.1.6 → 10.0.0-beta.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.
@@ -57,7 +57,7 @@ public class UpdateModule extends NativeUpdateSpec {
57
57
 
58
58
  @Override
59
59
  public void downloadFullUpdate(ReadableMap options, final Promise promise) {
60
- UpdateModuleImpl.downloadFullUpdate(this.updateContext,options,promise);
60
+ UpdateModuleImpl.downloadFullUpdate(this.updateContext,options,promise);
61
61
  }
62
62
 
63
63
  @Override
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "9.1.6",
3
+ "version": "10.0.0-beta.1",
4
4
  "description": "react-native hot update",
5
- "main": "lib/index.ts",
5
+ "main": "src/index.ts",
6
6
  "scripts": {
7
7
  "prepublish": "yarn submodule",
8
8
  "submodule": "git submodule update --init --recursive",
@@ -39,6 +39,7 @@
39
39
  "url": "https://github.com/reactnativecn/react-native-pushy/issues"
40
40
  },
41
41
  "peerDependencies": {
42
+ "react": ">=16.8.0",
42
43
  "react-native": ">=0.57.0"
43
44
  },
44
45
  "homepage": "https://github.com/reactnativecn/react-native-pushy#readme",
@@ -55,17 +56,21 @@
55
56
  ]
56
57
  },
57
58
  "devDependencies": {
59
+ "@react-native/eslint-config": "^0.73.2",
58
60
  "@types/fs-extra": "^9.0.13",
59
61
  "@types/jest": "^29.2.1",
60
62
  "@types/node": "^20.8.9",
61
- "@types/react": "^18.2.33",
63
+ "@types/react": "^18.2.46",
62
64
  "detox": "^20.5.0",
65
+ "eslint": "^8.56.0",
63
66
  "firebase-tools": "^11.24.1",
64
67
  "fs-extra": "^9.1.0",
65
68
  "jest": "^29.2.1",
66
69
  "pod-install": "^0.1.37",
67
- "react-native": "^0.72.6",
70
+ "prettier": "^2",
71
+ "react": "18.2.0",
72
+ "react-native": "0.73",
68
73
  "ts-jest": "^29.0.3",
69
- "typescript": "^5.2.2"
74
+ "typescript": "^5.3.3"
70
75
  }
71
76
  }
@@ -1,5 +1,7 @@
1
1
  require 'json'
2
2
 
3
+ new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
4
+
3
5
  package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
6
 
5
7
  Pod::Spec.new do |s|
@@ -40,5 +42,25 @@ Pod::Spec.new do |s|
40
42
  'android/jni/lzma/C/Lzma2Dec.{h,c}']
41
43
  ss.private_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
42
44
  end
43
- install_modules_dependencies(s)
45
+
46
+ if defined?(install_modules_dependencies()) != nil
47
+ install_modules_dependencies(s);
48
+ else
49
+ if new_arch_enabled
50
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
51
+
52
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
53
+
54
+ s.pod_target_xcconfig = {
55
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
56
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
57
+ }
58
+
59
+ s.dependency "React-Codegen"
60
+ s.dependency "RCT-Folly"
61
+ s.dependency "RCTRequired"
62
+ s.dependency "RCTTypeSafety"
63
+ s.dependency "ReactCommon/turbomodule/core"
64
+ end
65
+ end
44
66
  end
package/src/client.tsx ADDED
@@ -0,0 +1,316 @@
1
+ import { CheckResult, PushyOptions, ProgressData } from './type';
2
+ import { assertRelease, log } from './utils';
3
+ import {
4
+ EmitterSubscription,
5
+ PermissionsAndroid,
6
+ Platform,
7
+ } from 'react-native';
8
+ import {
9
+ PushyModule,
10
+ buildTime,
11
+ cInfo,
12
+ pushyNativeEventEmitter,
13
+ currentVersion,
14
+ packageVersion,
15
+ report,
16
+ rolledBackVersion,
17
+ setLocalHashInfo,
18
+ } from './core';
19
+
20
+ const defaultServer = {
21
+ main: 'https://update.react-native.cn/api',
22
+ backups: ['https://update.reactnative.cn/api'],
23
+ queryUrl:
24
+ 'https://raw.githubusercontent.com/reactnativecn/react-native-pushy/master/endpoints.json',
25
+ };
26
+
27
+ const empty = {};
28
+ export class Pushy {
29
+ options: PushyOptions = {
30
+ appKey: '',
31
+ server: defaultServer,
32
+ autoMarkSuccess: true,
33
+ useAlert: true,
34
+ };
35
+
36
+ lastChecking: number;
37
+ lastResult: CheckResult;
38
+
39
+ progressHandlers: Record<string, EmitterSubscription> = {};
40
+ downloadedHash: string;
41
+
42
+ marked = false;
43
+ applyingUpdate = false;
44
+ version = cInfo.pushy;
45
+
46
+ constructor(options: PushyOptions) {
47
+ if (!options.appKey) {
48
+ throw new Error('appKey is required');
49
+ }
50
+ this.setOptions(options);
51
+ }
52
+
53
+ setOptions = (options: Partial<PushyOptions>) => {
54
+ for (const [key, value] of Object.entries(options)) {
55
+ if (value !== undefined) {
56
+ this.options[key] = value;
57
+ }
58
+ }
59
+ };
60
+
61
+ getCheckUrl = (endpoint: string = this.options.server!.main) => {
62
+ return `${endpoint}/checkUpdate/${this.options.appKey}`;
63
+ };
64
+ assertHash = (hash: string) => {
65
+ if (!this.downloadedHash) {
66
+ return;
67
+ }
68
+ if (hash !== this.downloadedHash) {
69
+ log(`use downloaded hash ${this.downloadedHash} first`);
70
+ return;
71
+ }
72
+ return true;
73
+ };
74
+ markSuccess = () => {
75
+ assertRelease();
76
+ if (this.marked) {
77
+ return;
78
+ }
79
+ this.marked = true;
80
+ PushyModule.markSuccess();
81
+ report({ type: 'markSuccess' });
82
+ };
83
+ switchVersion = (hash: string) => {
84
+ assertRelease();
85
+ if (this.assertHash(hash) && !this.applyingUpdate) {
86
+ log('switchVersion: ' + hash);
87
+ this.applyingUpdate = true;
88
+ PushyModule.reloadUpdate({ hash });
89
+ }
90
+ };
91
+
92
+ switchVersionLater = (hash: string) => {
93
+ assertRelease();
94
+ if (this.assertHash(hash)) {
95
+ log('switchVersionLater: ' + hash);
96
+ PushyModule.setNeedUpdate({ hash });
97
+ }
98
+ };
99
+ checkUpdate = async () => {
100
+ assertRelease();
101
+ const now = Date.now();
102
+ if (
103
+ this.lastResult &&
104
+ this.lastChecking &&
105
+ now - this.lastChecking < 1000 * 5
106
+ ) {
107
+ return this.lastResult;
108
+ }
109
+ this.lastChecking = now;
110
+ report({ type: 'checking' });
111
+ const fetchPayload = {
112
+ method: 'POST',
113
+ headers: {
114
+ Accept: 'application/json',
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ body: JSON.stringify({
118
+ packageVersion,
119
+ hash: currentVersion,
120
+ buildTime,
121
+ cInfo,
122
+ }),
123
+ };
124
+ let resp;
125
+ try {
126
+ resp = await fetch(this.getCheckUrl(), fetchPayload);
127
+ } catch (e) {
128
+ report({
129
+ type: 'errorChecking',
130
+ message: 'Can not connect to update server. Trying backup endpoints.',
131
+ });
132
+ const backupEndpoints = await this.getBackupEndpoints();
133
+ if (backupEndpoints) {
134
+ try {
135
+ resp = await Promise.race(
136
+ backupEndpoints.map((endpoint) =>
137
+ fetch(this.getCheckUrl(endpoint), fetchPayload),
138
+ ),
139
+ );
140
+ } catch {}
141
+ }
142
+ }
143
+ if (!resp) {
144
+ report({
145
+ type: 'errorChecking',
146
+ message: 'Can not connect to update server. Please check your network.',
147
+ });
148
+ return this.lastResult || empty;
149
+ }
150
+ const result: CheckResult = await resp.json();
151
+
152
+ this.lastResult = result;
153
+
154
+ if (resp.status !== 200) {
155
+ report({
156
+ type: 'errorChecking',
157
+ //@ts-ignore
158
+ message: result.message,
159
+ });
160
+ }
161
+
162
+ return result;
163
+ };
164
+ getBackupEndpoints = async () => {
165
+ const { server } = this.options;
166
+ if (!server) {
167
+ return [];
168
+ }
169
+ if (server.queryUrl) {
170
+ try {
171
+ const resp = await fetch(server.queryUrl);
172
+ const remoteEndpoints = await resp.json();
173
+ log('fetch endpoints:', remoteEndpoints);
174
+ if (Array.isArray(remoteEndpoints)) {
175
+ server.backups = Array.from(
176
+ new Set([...(server.backups || []), ...remoteEndpoints]),
177
+ );
178
+ }
179
+ } catch (e) {
180
+ log('failed to fetch endpoints from: ', server.queryUrl);
181
+ }
182
+ }
183
+ return server.backups;
184
+ };
185
+ downloadUpdate = async (
186
+ info: CheckResult,
187
+ onDownloadProgress?: (data: ProgressData) => void,
188
+ ) => {
189
+ assertRelease();
190
+ if (!('update' in info)) {
191
+ return;
192
+ }
193
+ const { hash, diffUrl, pdiffUrl, updateUrl, name, description, metaInfo } =
194
+ info;
195
+ if (rolledBackVersion === hash) {
196
+ log(`rolledback hash ${rolledBackVersion}, ignored`);
197
+ return;
198
+ }
199
+ if (this.downloadedHash === hash) {
200
+ log(`duplicated downloaded hash ${this.downloadedHash}, ignored`);
201
+ return this.downloadedHash;
202
+ }
203
+ if (this.progressHandlers[hash]) {
204
+ return;
205
+ }
206
+ if (onDownloadProgress) {
207
+ this.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
208
+ 'RCTPushyDownloadProgress',
209
+ (progressData) => {
210
+ if (progressData.hash === hash) {
211
+ onDownloadProgress(progressData);
212
+ }
213
+ },
214
+ );
215
+ }
216
+ let succeeded = false;
217
+ report({ type: 'downloading' });
218
+ if (diffUrl) {
219
+ log('downloading diff');
220
+ try {
221
+ await PushyModule.downloadPatchFromPpk({
222
+ updateUrl: diffUrl,
223
+ hash,
224
+ originHash: currentVersion,
225
+ });
226
+ succeeded = true;
227
+ } catch (e) {
228
+ log(`diff error: ${e.message}, try pdiff`);
229
+ }
230
+ }
231
+ if (!succeeded && pdiffUrl) {
232
+ log('downloading pdiff');
233
+ try {
234
+ await PushyModule.downloadPatchFromPackage({
235
+ updateUrl: pdiffUrl,
236
+ hash,
237
+ });
238
+ succeeded = true;
239
+ } catch (e) {
240
+ log(`pdiff error: ${e.message}, try full patch`);
241
+ }
242
+ }
243
+ if (!succeeded && updateUrl) {
244
+ log('downloading full patch');
245
+ try {
246
+ await PushyModule.downloadFullUpdate({
247
+ updateUrl: updateUrl,
248
+ hash,
249
+ });
250
+ succeeded = true;
251
+ } catch (e) {
252
+ log(`full patch error: ${e.message}`);
253
+ }
254
+ }
255
+ if (this.progressHandlers[hash]) {
256
+ this.progressHandlers[hash].remove();
257
+ delete this.progressHandlers[hash];
258
+ }
259
+ if (!succeeded) {
260
+ return report({
261
+ type: 'errorUpdate',
262
+ data: { newVersion: hash },
263
+ });
264
+ }
265
+ setLocalHashInfo(hash, {
266
+ name,
267
+ description,
268
+ metaInfo,
269
+ });
270
+ this.downloadedHash = hash;
271
+ return hash;
272
+ };
273
+ downloadAndInstallApk = async (
274
+ url: string,
275
+ onDownloadProgress?: (data: ProgressData) => void,
276
+ ) => {
277
+ if (Platform.OS !== 'android') {
278
+ return;
279
+ }
280
+ report({ type: 'downloadingApk' });
281
+ if (Platform.Version <= 23) {
282
+ try {
283
+ const granted = await PermissionsAndroid.request(
284
+ PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
285
+ );
286
+ if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
287
+ return report({ type: 'rejectStoragePermission' });
288
+ }
289
+ } catch (err) {
290
+ return report({ type: 'errorStoragePermission' });
291
+ }
292
+ }
293
+ const progressKey = 'downloadingApk';
294
+ if (onDownloadProgress) {
295
+ this.progressHandlers[progressKey] = pushyNativeEventEmitter.addListener(
296
+ 'RCTPushyDownloadProgress',
297
+ (progressData: ProgressData) => {
298
+ if (progressData.hash === progressKey) {
299
+ onDownloadProgress(progressData);
300
+ }
301
+ },
302
+ );
303
+ }
304
+ await PushyModule.downloadAndInstallApk({
305
+ url,
306
+ target: 'update.apk',
307
+ hash: progressKey,
308
+ }).catch(() => {
309
+ report({ type: 'errowDownloadAndInstallApk' });
310
+ });
311
+ if (this.progressHandlers[progressKey]) {
312
+ this.progressHandlers[progressKey].remove();
313
+ delete this.progressHandlers[progressKey];
314
+ }
315
+ };
316
+ }
package/src/context.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { CheckResult, ProgressData } from './type';
3
+ import { Pushy } from './client';
4
+
5
+ const empty = {};
6
+ const noop = () => {};
7
+
8
+ export const defaultContext = {
9
+ checkUpdate: () => Promise.resolve(empty),
10
+ switchVersion: noop,
11
+ switchVersionLater: noop,
12
+ markSuccess: noop,
13
+ dismissError: noop,
14
+ downloadUpdate: noop,
15
+ currentHash: '',
16
+ packageVersion: '',
17
+ };
18
+
19
+ export const PushyContext = createContext<{
20
+ checkUpdate: () => void;
21
+ switchVersion: () => void;
22
+ switchVersionLater: () => void;
23
+ markSuccess: () => void;
24
+ dismissError: () => void;
25
+ downloadUpdate: () => void;
26
+ currentHash: string;
27
+ packageVersion: string;
28
+ client?: Pushy;
29
+ progress?: ProgressData;
30
+ updateInfo?: CheckResult;
31
+ lastError?: Error;
32
+ }>(defaultContext);
33
+
34
+ export const usePushy = () => useContext(PushyContext);
package/src/core.ts ADDED
@@ -0,0 +1,106 @@
1
+ import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
2
+ import { EventType, UpdateEventsLogger } from './type';
3
+ import { log } from './utils';
4
+ const {
5
+ version: v,
6
+ } = require('react-native/Libraries/Core/ReactNativeVersion');
7
+ const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
8
+ const isTurboModuleEnabled = global.__turboModuleProxy != null;
9
+
10
+ export const PushyModule = isTurboModuleEnabled
11
+ ? require('./turboModuleSpec').default
12
+ : NativeModules.Pushy;
13
+
14
+ if (!PushyModule) {
15
+ throw new Error('react-native-update模块无法加载,请对照安装文档检查配置。');
16
+ }
17
+
18
+ const PushyConstants = isTurboModuleEnabled
19
+ ? PushyModule.getConstants()
20
+ : PushyModule;
21
+
22
+ export const downloadRootDir = PushyConstants.downloadRootDir;
23
+ export const packageVersion = PushyConstants.packageVersion;
24
+ export const currentVersion = PushyConstants.currentVersion;
25
+ export const isFirstTime = PushyConstants.isFirstTime;
26
+ export const rolledBackVersion = PushyConstants.rolledBackVersion;
27
+ export const isRolledBack = typeof rolledBackVersion === 'string';
28
+
29
+ export const buildTime = PushyConstants.buildTime;
30
+ let uuid = PushyConstants.uuid;
31
+
32
+ if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
33
+ throw new Error(
34
+ 'react-native-update模块无法加载,请对照文档检查Bundle URL的配置',
35
+ );
36
+ }
37
+
38
+ export function setLocalHashInfo(hash: string, info: Record<string, any>) {
39
+ PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
40
+ }
41
+
42
+ async function getLocalHashInfo(hash: string) {
43
+ return JSON.parse(await PushyModule.getLocalHashInfo(hash));
44
+ }
45
+
46
+ export async function getCurrentVersionInfo(): Promise<{
47
+ name?: string;
48
+ description?: string;
49
+ metaInfo?: string;
50
+ }> {
51
+ return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
52
+ }
53
+
54
+ export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
55
+
56
+ if (!uuid) {
57
+ uuid = require('nanoid/non-secure').nanoid();
58
+ PushyModule.setUuid(uuid);
59
+ }
60
+
61
+ const noop = () => {};
62
+ let reporter: UpdateEventsLogger = noop;
63
+
64
+ export function onPushyEvents(customReporter: UpdateEventsLogger) {
65
+ reporter = customReporter;
66
+ if (isRolledBack) {
67
+ report({
68
+ type: 'rollback',
69
+ data: {
70
+ rolledBackVersion,
71
+ },
72
+ });
73
+ }
74
+ }
75
+
76
+ export function report({
77
+ type,
78
+ message = '',
79
+ data = {},
80
+ }: {
81
+ type: EventType;
82
+ message?: string;
83
+ data?: Record<string, string | number>;
84
+ }) {
85
+ log(type + ' ' + message);
86
+ reporter({
87
+ type,
88
+ data: {
89
+ currentVersion,
90
+ cInfo,
91
+ packageVersion,
92
+ buildTime,
93
+ message,
94
+ ...data,
95
+ },
96
+ });
97
+ }
98
+
99
+ log('uuid: ' + uuid);
100
+
101
+ export const cInfo = {
102
+ pushy: require('../package.json').version,
103
+ rn: RNVersion,
104
+ os: Platform.OS + ' ' + Platform.Version,
105
+ uuid,
106
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { Pushy } from './client';
2
+ export { PushyContext, usePushy } from './context';
3
+ export { PushyProvider } from './provider';
@@ -0,0 +1,17 @@
1
+ import { Fragment } from 'react';
2
+
3
+ const noop = () => {};
4
+ export class Pushy {
5
+ constructor() {
6
+ console.warn('react-native-update is not supported and will do nothing on web.');
7
+ return new Proxy(this, {
8
+ get() {
9
+ return noop;
10
+ },
11
+ });
12
+ }
13
+ }
14
+
15
+ export { PushyContext, usePushy } from './context';
16
+
17
+ export const PushyProvider = Fragment;