react-native-update 10.38.3 → 10.38.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.
@@ -0,0 +1,346 @@
1
+ import preferences from '@ohos.data.preferences';
2
+ import fileIo from '@ohos.file.fs';
3
+ import { DownloadTask } from './DownloadTask';
4
+ import common from '@ohos.app.ability.common';
5
+ import { DownloadTaskParams } from './DownloadTaskParams';
6
+ import NativePatchCore, {
7
+ STATE_OP_CLEAR_FIRST_TIME,
8
+ STATE_OP_CLEAR_ROLLBACK_MARK,
9
+ STATE_OP_MARK_SUCCESS,
10
+ STATE_OP_RESOLVE_LAUNCH,
11
+ STATE_OP_ROLLBACK,
12
+ STATE_OP_SWITCH_VERSION,
13
+ StateCoreResult,
14
+ } from './NativePatchCore';
15
+
16
+ export class UpdateContext {
17
+ private context: common.UIAbilityContext;
18
+ private rootDir: string;
19
+ private preferences: preferences.Preferences;
20
+ private static DEBUG: boolean = false;
21
+ private static isUsingBundleUrl: boolean = false;
22
+
23
+ constructor(context: common.UIAbilityContext) {
24
+ this.context = context;
25
+ this.rootDir = context.filesDir + '/_update';
26
+
27
+ try {
28
+ if (!fileIo.accessSync(this.rootDir)) {
29
+ fileIo.mkdirSync(this.rootDir);
30
+ }
31
+ } catch (e) {
32
+ console.error('Failed to create root directory:', e);
33
+ }
34
+ this.initPreferences();
35
+ }
36
+
37
+ private initPreferences() {
38
+ try {
39
+ this.preferences = preferences.getPreferencesSync(this.context, {
40
+ name: 'update',
41
+ });
42
+ } catch (e) {
43
+ console.error('Failed to init preferences:', e);
44
+ }
45
+ }
46
+
47
+ private readString(key: string): string {
48
+ const value = this.preferences.getSync(key, '') as
49
+ | string
50
+ | boolean
51
+ | number
52
+ | null
53
+ | undefined;
54
+ if (typeof value === 'string') {
55
+ return value;
56
+ }
57
+ if (typeof value === 'number') {
58
+ return String(value);
59
+ }
60
+ if (typeof value === 'boolean') {
61
+ return value ? 'true' : 'false';
62
+ }
63
+ return '';
64
+ }
65
+
66
+ private readBoolean(key: string, defaultValue: boolean): boolean {
67
+ const value = this.preferences.getSync(key, defaultValue) as
68
+ | string
69
+ | boolean
70
+ | number
71
+ | null
72
+ | undefined;
73
+ if (typeof value === 'boolean') {
74
+ return value;
75
+ }
76
+ if (typeof value === 'string') {
77
+ if (value === 'true') {
78
+ return true;
79
+ }
80
+ if (value === 'false') {
81
+ return false;
82
+ }
83
+ }
84
+ if (typeof value === 'number') {
85
+ return value !== 0;
86
+ }
87
+ return defaultValue;
88
+ }
89
+
90
+ private putNullableString(key: string, value?: string): void {
91
+ if (value) {
92
+ this.preferences.putSync(key, value);
93
+ return;
94
+ }
95
+ this.preferences.deleteSync(key);
96
+ }
97
+
98
+ private getBundlePath(hash: string): string {
99
+ return `${this.rootDir}/${hash}/bundle.harmony.js`;
100
+ }
101
+
102
+ private getStateSnapshot(): StateCoreResult {
103
+ return {
104
+ packageVersion: this.readString('packageVersion'),
105
+ buildTime: this.readString('buildTime'),
106
+ currentVersion: this.readString('currentVersion'),
107
+ lastVersion: this.readString('lastVersion'),
108
+ firstTime: this.readBoolean('firstTime', false),
109
+ firstTimeOk: this.readBoolean('firstTimeOk', true),
110
+ rolledBackVersion: this.readString('rolledBackVersion'),
111
+ };
112
+ }
113
+
114
+ private applyState(state: StateCoreResult): void {
115
+ this.putNullableString('packageVersion', state.packageVersion);
116
+ this.putNullableString('buildTime', state.buildTime);
117
+ this.putNullableString('currentVersion', state.currentVersion);
118
+ this.putNullableString('lastVersion', state.lastVersion);
119
+ this.preferences.putSync('firstTime', !!state.firstTime);
120
+ this.preferences.putSync('firstTimeOk', state.firstTimeOk !== false);
121
+ this.putNullableString('rolledBackVersion', state.rolledBackVersion);
122
+ }
123
+
124
+ public syncStateWithBinaryVersion(
125
+ packageVersion: string,
126
+ buildTime: string,
127
+ ): void {
128
+ const currentState = this.getStateSnapshot();
129
+ const nextState = NativePatchCore.syncStateWithBinaryVersion(
130
+ packageVersion,
131
+ buildTime,
132
+ currentState,
133
+ );
134
+ if (!nextState.changed) {
135
+ return;
136
+ }
137
+
138
+ this.cleanUp();
139
+ this.preferences.clear();
140
+ this.applyState(nextState);
141
+ this.preferences.flush();
142
+ }
143
+
144
+ public setKv(key: string, value: string): void {
145
+ this.preferences.putSync(key, value);
146
+ this.preferences.flush();
147
+ }
148
+
149
+ public getKv(key: string): string {
150
+ return this.readString(key);
151
+ }
152
+
153
+ public isFirstTime(): boolean {
154
+ return this.getStateSnapshot().firstTime;
155
+ }
156
+
157
+ public rolledBackVersion(): string {
158
+ return this.getStateSnapshot().rolledBackVersion || '';
159
+ }
160
+
161
+ public markSuccess(): void {
162
+ if (UpdateContext.DEBUG) {
163
+ return;
164
+ }
165
+
166
+ const nextState = NativePatchCore.runStateCore(
167
+ STATE_OP_MARK_SUCCESS,
168
+ this.getStateSnapshot(),
169
+ );
170
+ this.applyState(nextState);
171
+ if (nextState.staleVersionToDelete) {
172
+ this.preferences.deleteSync(`hash_${nextState.staleVersionToDelete}`);
173
+ }
174
+ this.preferences.flush();
175
+ this.cleanUp();
176
+ }
177
+
178
+ public clearFirstTime(): void {
179
+ const nextState = NativePatchCore.runStateCore(
180
+ STATE_OP_CLEAR_FIRST_TIME,
181
+ this.getStateSnapshot(),
182
+ );
183
+ this.applyState(nextState);
184
+ this.preferences.flush();
185
+ this.cleanUp();
186
+ }
187
+
188
+ public clearRollbackMark(): void {
189
+ const nextState = NativePatchCore.runStateCore(
190
+ STATE_OP_CLEAR_ROLLBACK_MARK,
191
+ this.getStateSnapshot(),
192
+ );
193
+ this.applyState(nextState);
194
+ this.preferences.flush();
195
+ this.cleanUp();
196
+ }
197
+
198
+ public async downloadFullUpdate(url: string, hash: string): Promise<void> {
199
+ try {
200
+ const params = new DownloadTaskParams();
201
+ params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
202
+ params.url = url;
203
+ params.hash = hash;
204
+ params.targetFile = `${this.rootDir}/${hash}.ppk`;
205
+ params.unzipDirectory = `${this.rootDir}/${hash}`;
206
+ const downloadTask = new DownloadTask(this.context);
207
+ await downloadTask.execute(params);
208
+ } catch (e) {
209
+ console.error('Failed to download full update:', e);
210
+ throw e;
211
+ }
212
+ }
213
+
214
+ public async downloadFile(
215
+ url: string,
216
+ hash: string,
217
+ fileName: string,
218
+ ): Promise<void> {
219
+ const params = new DownloadTaskParams();
220
+ params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
221
+ params.url = url;
222
+ params.hash = hash;
223
+ params.targetFile = this.rootDir + '/' + fileName;
224
+
225
+ const downloadTask = new DownloadTask(this.context);
226
+ await downloadTask.execute(params);
227
+ }
228
+
229
+ public async downloadPatchFromPpk(
230
+ url: string,
231
+ hash: string,
232
+ originHash: string,
233
+ ): Promise<void> {
234
+ const params = new DownloadTaskParams();
235
+ params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
236
+ params.url = url;
237
+ params.hash = hash;
238
+ params.originHash = originHash;
239
+ params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
240
+ params.unzipDirectory = `${this.rootDir}/${hash}`;
241
+ params.originDirectory = `${this.rootDir}/${params.originHash}`;
242
+
243
+ const downloadTask = new DownloadTask(this.context);
244
+ await downloadTask.execute(params);
245
+ }
246
+
247
+ public async downloadPatchFromPackage(
248
+ url: string,
249
+ hash: string,
250
+ ): Promise<void> {
251
+ try {
252
+ const params = new DownloadTaskParams();
253
+ params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
254
+ params.url = url;
255
+ params.hash = hash;
256
+ params.targetFile = `${this.rootDir}/${hash}.app.patch`;
257
+ params.unzipDirectory = `${this.rootDir}/${hash}`;
258
+
259
+ const downloadTask = new DownloadTask(this.context);
260
+ return await downloadTask.execute(params);
261
+ } catch (e) {
262
+ console.error('Failed to download package patch:', e);
263
+ throw e;
264
+ }
265
+ }
266
+
267
+ public switchVersion(hash: string): void {
268
+ try {
269
+ const bundlePath = this.getBundlePath(hash);
270
+ if (!fileIo.accessSync(bundlePath)) {
271
+ throw Error(`Bundle version ${hash} not found.`);
272
+ }
273
+
274
+ const nextState = NativePatchCore.runStateCore(
275
+ STATE_OP_SWITCH_VERSION,
276
+ this.getStateSnapshot(),
277
+ hash,
278
+ );
279
+ this.applyState(nextState);
280
+ this.preferences.flush();
281
+ } catch (e) {
282
+ console.error('Failed to switch version:', e);
283
+ throw e;
284
+ }
285
+ }
286
+
287
+ public getBundleUrl() {
288
+ UpdateContext.isUsingBundleUrl = true;
289
+ const launchState = NativePatchCore.runStateCore(
290
+ STATE_OP_RESOLVE_LAUNCH,
291
+ this.getStateSnapshot(),
292
+ '',
293
+ false,
294
+ false,
295
+ );
296
+ if (launchState.didRollback || launchState.consumedFirstTime) {
297
+ this.applyState(launchState);
298
+ this.preferences.flush();
299
+ }
300
+
301
+ let version = launchState.loadVersion || '';
302
+ while (version) {
303
+ const bundleFile = this.getBundlePath(version);
304
+ try {
305
+ if (!fileIo.accessSync(bundleFile)) {
306
+ console.error(`Bundle version ${version} not found.`);
307
+ version = this.rollBack();
308
+ continue;
309
+ }
310
+ return bundleFile;
311
+ } catch (e) {
312
+ console.error('Failed to access bundle file:', e);
313
+ version = this.rollBack();
314
+ }
315
+ }
316
+ return '';
317
+ }
318
+
319
+ public getCurrentVersion(): string {
320
+ return this.getStateSnapshot().currentVersion || '';
321
+ }
322
+
323
+ private rollBack(): string {
324
+ const nextState = NativePatchCore.runStateCore(
325
+ STATE_OP_ROLLBACK,
326
+ this.getStateSnapshot(),
327
+ );
328
+ this.applyState(nextState);
329
+ this.preferences.flush();
330
+ return nextState.currentVersion || '';
331
+ }
332
+
333
+ public cleanUp(): void {
334
+ const state = this.getStateSnapshot();
335
+ NativePatchCore.cleanupOldEntries(
336
+ this.rootDir,
337
+ state.currentVersion || '',
338
+ state.lastVersion || '',
339
+ 7,
340
+ );
341
+ }
342
+
343
+ public getIsUsingBundleUrl(): boolean {
344
+ return UpdateContext.isUsingBundleUrl;
345
+ }
346
+ }
@@ -0,0 +1,123 @@
1
+ import bundleManager from '@ohos.bundle.bundleManager';
2
+ import common from '@ohos.app.ability.common';
3
+ import { UpdateContext } from './UpdateContext';
4
+ import logger from './Logger';
5
+
6
+ const TAG = 'UpdateModuleImpl';
7
+
8
+ export class UpdateModuleImpl {
9
+ static readonly NAME = 'Pushy';
10
+
11
+ static async downloadFullUpdate(
12
+ updateContext: UpdateContext,
13
+ options: { updateUrl: string; hash: string },
14
+ ): Promise<void> {
15
+ return updateContext.downloadFullUpdate(options.updateUrl, options.hash);
16
+ }
17
+
18
+ static async downloadPatchFromPackage(
19
+ updateContext: UpdateContext,
20
+ options: { updateUrl: string; hash: string },
21
+ ): Promise<void> {
22
+ return updateContext.downloadPatchFromPackage(
23
+ options.updateUrl,
24
+ options.hash,
25
+ );
26
+ }
27
+
28
+ static async downloadPatchFromPpk(
29
+ updateContext: UpdateContext,
30
+ options: { updateUrl: string; hash: string; originHash: string },
31
+ ): Promise<void> {
32
+ return updateContext.downloadPatchFromPpk(
33
+ options.updateUrl,
34
+ options.hash,
35
+ options.originHash,
36
+ );
37
+ }
38
+
39
+ static async reloadUpdate(
40
+ updateContext: UpdateContext,
41
+ context: common.UIAbilityContext,
42
+ options: { hash: string },
43
+ ): Promise<void> {
44
+ const hash = options.hash;
45
+ if (!hash) {
46
+ throw Error('hash不能为空');
47
+ }
48
+
49
+ try {
50
+ await updateContext.switchVersion(hash);
51
+ const bundleInfo = await bundleManager.getBundleInfoForSelf(
52
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION,
53
+ );
54
+ await context.terminateSelf();
55
+ const want = {
56
+ bundleName: bundleInfo.name,
57
+ abilityName: context.abilityInfo?.name,
58
+ };
59
+ await context.startAbility(want);
60
+ } catch (error) {
61
+ logger.error(TAG, `reloadUpdate failed: ${error}`);
62
+ throw Error(`switchVersion failed ${error.message}`);
63
+ }
64
+ }
65
+
66
+ static async setNeedUpdate(
67
+ updateContext: UpdateContext,
68
+ options: { hash: string },
69
+ ): Promise<boolean> {
70
+ const hash = options.hash;
71
+ if (!hash) {
72
+ throw Error('empty hash');
73
+ }
74
+
75
+ try {
76
+ await updateContext.switchVersion(hash);
77
+ return true;
78
+ } catch (error) {
79
+ logger.error(TAG, `setNeedUpdate failed: ${error}`);
80
+ throw Error(`switchVersionLater failed: ${error.message}`);
81
+ }
82
+ }
83
+
84
+ static async markSuccess(updateContext: UpdateContext): Promise<boolean> {
85
+ try {
86
+ await updateContext.markSuccess();
87
+ return true;
88
+ } catch (error) {
89
+ logger.error(TAG, `markSuccess failed: ${error}`);
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ static async setUuid(
95
+ updateContext: UpdateContext,
96
+ uuid: string,
97
+ ): Promise<void> {
98
+ return updateContext.setKv('uuid', uuid);
99
+ }
100
+
101
+ static checkJson(json: string): boolean {
102
+ try {
103
+ JSON.parse(json);
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ static setLocalHashInfo(
111
+ updateContext: UpdateContext,
112
+ hash: string,
113
+ info: string,
114
+ ): boolean {
115
+ updateContext.setKv(`hash_${hash}`, info);
116
+ return true;
117
+ }
118
+
119
+ static getLocalHashInfo(updateContext: UpdateContext, hash: string): string {
120
+ const value = updateContext.getKv(`hash_${hash}`);
121
+ return value;
122
+ }
123
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "module": {
3
+ "name": "pushy",
4
+ "type": "har",
5
+ "deviceTypes": ['default'],
6
+ },
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "string": [
3
+ {
4
+ "name": "page_show",
5
+ "value": "page from npm package"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "string": [
3
+ {
4
+ "name": "page_show",
5
+ "value": "page from npm package"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "string": [
3
+ {
4
+ "name": "page_show",
5
+ "value": "page from npm package"
6
+ }
7
+ ]
8
+ }
package/harmony/pushy.har CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.38.3",
3
+ "version": "10.38.5",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
package/src/client.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  ClientOptions,
23
23
  EventType,
24
24
  ProgressData,
25
+ UpdateCheckState,
25
26
  UpdateServerConfig,
26
27
  } from './type';
27
28
  import {
@@ -207,6 +208,16 @@ export class Pushy {
207
208
  throw e;
208
209
  }
209
210
  };
211
+ notifyAfterCheckUpdate = (state: UpdateCheckState) => {
212
+ const { afterCheckUpdate } = this.options;
213
+ if (!afterCheckUpdate) {
214
+ return;
215
+ }
216
+ // 这里仅做状态通知,不阻塞原有检查流程
217
+ Promise.resolve(afterCheckUpdate(state)).catch((error: any) => {
218
+ log('afterCheckUpdate failed:', error?.message || error);
219
+ });
220
+ };
210
221
  getCheckUrl = (endpoint: string) => {
211
222
  return `${endpoint}/checkUpdate/${this.options.appKey}`;
212
223
  };
@@ -329,9 +340,11 @@ export class Pushy {
329
340
  };
330
341
  checkUpdate = async (extra?: Record<string, any>) => {
331
342
  if (!this.assertDebug('checkUpdate()')) {
343
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
332
344
  return;
333
345
  }
334
346
  if (!assertWeb()) {
347
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
335
348
  return;
336
349
  }
337
350
  if (
@@ -339,6 +352,7 @@ export class Pushy {
339
352
  (await this.options.beforeCheckUpdate()) === false
340
353
  ) {
341
354
  log('beforeCheckUpdate returned false, skipping check');
355
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
342
356
  return;
343
357
  }
344
358
  const now = Date.now();
@@ -347,7 +361,9 @@ export class Pushy {
347
361
  this.lastChecking &&
348
362
  now - this.lastChecking < 1000 * 5
349
363
  ) {
350
- return await this.lastRespJson;
364
+ const result = await this.lastRespJson;
365
+ this.notifyAfterCheckUpdate({ status: 'completed', result });
366
+ return result;
351
367
  }
352
368
  this.lastChecking = now;
353
369
  const fetchBody = {
@@ -387,6 +403,7 @@ export class Pushy {
387
403
 
388
404
  log('checking result:', result);
389
405
 
406
+ this.notifyAfterCheckUpdate({ status: 'completed', result });
390
407
  return result;
391
408
  } catch (e: any) {
392
409
  this.lastRespJson = previousRespJson;
@@ -396,6 +413,7 @@ export class Pushy {
396
413
  type: 'errorChecking',
397
414
  message: errorMessage,
398
415
  });
416
+ this.notifyAfterCheckUpdate({ status: 'error', error: e });
399
417
  this.throwIfEnabled(e);
400
418
  return previousRespJson ? await previousRespJson : emptyObj;
401
419
  }
package/src/provider.tsx CHANGED
@@ -165,6 +165,7 @@ export const UpdateProvider = ({
165
165
  async ({ extra }: { extra?: Partial<{ toHash: string }> } = {}) => {
166
166
  const now = Date.now();
167
167
  if (lastChecking.current && now - lastChecking.current < 1000) {
168
+ client.notifyAfterCheckUpdate({ status: 'skipped' });
168
169
  return;
169
170
  }
170
171
  lastChecking.current = now;
package/src/type.ts CHANGED
@@ -35,6 +35,13 @@ export interface ProgressData {
35
35
  total: number;
36
36
  }
37
37
 
38
+ // 用于描述一次检查结束后的最终状态,便于业务侧感知成功、跳过或失败
39
+ export interface UpdateCheckState {
40
+ status: 'completed' | 'skipped' | 'error';
41
+ result?: CheckResult;
42
+ error?: Error;
43
+ }
44
+
38
45
  export type EventType =
39
46
  | 'rollback'
40
47
  | 'errorChecking'
@@ -98,6 +105,8 @@ export interface ClientOptions {
98
105
  debug?: boolean;
99
106
  throwError?: boolean;
100
107
  beforeCheckUpdate?: () => Promise<boolean> | boolean;
108
+ // 每次检查结束后都会触发,不影响原有检查流程
109
+ afterCheckUpdate?: (state: UpdateCheckState) => Promise<void> | void;
101
110
  beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
102
111
  afterDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
103
112
  onPackageExpired?: (info: CheckResult) => Promise<boolean> | boolean;