react-native-update 10.42.6 → 10.42.9

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.
@@ -12,7 +12,7 @@ export class PushyFileJSBundleProvider extends JSBundleProvider {
12
12
 
13
13
  constructor(context: common.UIAbilityContext) {
14
14
  super();
15
- this.updateContext = new UpdateContext(context);
15
+ this.updateContext = UpdateContext.getInstance(context);
16
16
  }
17
17
 
18
18
  getURL(): string {
@@ -38,7 +38,7 @@ export class PushyTurboModule extends UITurboModule {
38
38
  super(ctx);
39
39
  logger.debug(TAG, ',PushyTurboModule constructor');
40
40
  this.mUiCtx = ctx.uiAbilityContext;
41
- this.context = new UpdateContext(this.mUiCtx);
41
+ this.context = UpdateContext.getInstance(this.mUiCtx);
42
42
  EventHub.getInstance().setRNInstance(ctx.rnInstance);
43
43
  }
44
44
 
@@ -78,6 +78,7 @@ export class PushyTurboModule extends UITurboModule {
78
78
 
79
79
  getConstants(): Object {
80
80
  logger.debug(TAG, ',call getConstants');
81
+ this.context.logStateSnapshot('getConstants:enter');
81
82
  const packageVersion = this.context.getPackageVersion();
82
83
  const buildTime = this.context.getBuildTime();
83
84
  this.context.syncStateWithBinaryVersion(packageVersion, buildTime);
@@ -95,7 +96,7 @@ export class PushyTurboModule extends UITurboModule {
95
96
  this.context.clearRollbackMark();
96
97
  }
97
98
 
98
- return {
99
+ const result = {
99
100
  downloadRootDir: `${this.mUiCtx.filesDir}/_update`,
100
101
  currentVersionInfo,
101
102
  packageVersion,
@@ -106,6 +107,19 @@ export class PushyTurboModule extends UITurboModule {
106
107
  rolledBackVersion,
107
108
  uuid,
108
109
  };
110
+ const logResult = {
111
+ downloadRootDir: result.downloadRootDir,
112
+ currentVersionInfo: result.currentVersionInfo,
113
+ packageVersion: result.packageVersion,
114
+ currentVersion: result.currentVersion,
115
+ buildTime: result.buildTime,
116
+ isUsingBundleUrl: result.isUsingBundleUrl,
117
+ isFirstTime: result.isFirstTime,
118
+ rolledBackVersion: result.rolledBackVersion,
119
+ uuidSet: !!result.uuid,
120
+ };
121
+ logger.info(TAG, `,getConstants result: ${JSON.stringify(logResult)}`);
122
+ return result;
109
123
  }
110
124
 
111
125
  setLocalHashInfo(hash: string, info: string): boolean {
@@ -5,6 +5,7 @@ import common from '@ohos.app.ability.common';
5
5
  import { DownloadTaskParams } from './DownloadTaskParams';
6
6
  import { bundleManager } from '@kit.AbilityKit';
7
7
  import { util } from '@kit.ArkTS';
8
+ import logger from './Logger';
8
9
  import NativePatchCore, {
9
10
  STATE_OP_CLEAR_FIRST_TIME,
10
11
  STATE_OP_CLEAR_ROLLBACK_MARK,
@@ -26,10 +27,25 @@ export class UpdateContext {
26
27
  private static DEBUG: boolean = false;
27
28
  private static isUsingBundleUrl: boolean = false;
28
29
  private static ignoreRollback: boolean = false;
30
+ private static cachedPackageVersion: string = '';
31
+ private static cachedBuildTime: string = '';
32
+ // 单例:确保 bundle provider 与 TurboModule 共用同一份 preferences 内存状态,
33
+ // 避免 RNOH RN 实例重建后两处 UpdateContext 各自持有 preferences 缓存导致读写分裂。
34
+ private static instance: UpdateContext | null = null;
35
+ private static instanceCounter: number = 0;
36
+ private readonly instanceId: string;
37
+
38
+ public static getInstance(context: common.UIAbilityContext): UpdateContext {
39
+ if (!UpdateContext.instance) {
40
+ UpdateContext.instance = new UpdateContext(context);
41
+ }
42
+ return UpdateContext.instance;
43
+ }
29
44
 
30
- constructor(context: common.UIAbilityContext) {
45
+ private constructor(context: common.UIAbilityContext) {
31
46
  this.context = context;
32
47
  this.rootDir = context.filesDir + '/_update';
48
+ this.instanceId = `uc#${++UpdateContext.instanceCounter}`;
33
49
 
34
50
  try {
35
51
  if (!fileIo.accessSync(this.rootDir)) {
@@ -39,12 +55,36 @@ export class UpdateContext {
39
55
  console.error('Failed to create root directory:', e);
40
56
  }
41
57
  this.initPreferences();
58
+ this.trace('ctor');
42
59
  this.syncStateWithBinaryVersion(
43
60
  this.getPackageVersion(),
44
61
  this.getBuildTime(),
45
62
  );
46
63
  }
47
64
 
65
+ /**
66
+ * 诊断日志:打印本实例 id 与关键状态,用于定位 preferences 多实例 / 状态分裂问题。
67
+ * 通过 hilog 输出,prefix=pushy,可在 hilog 中按 "UpdateContext" 过滤。
68
+ */
69
+ private trace(point: string): void {
70
+ const snap = this.getStateSnapshot();
71
+ logger.info(
72
+ 'UpdateContext',
73
+ `trace id=${this.instanceId} ${point}` +
74
+ ` pkg=${snap.packageVersion} bt=${snap.buildTime}` +
75
+ ` cv=${snap.currentVersion} lv=${snap.lastVersion}` +
76
+ ` ft=${snap.firstTime} fto=${snap.firstTimeOk}` +
77
+ ` rb=${snap.rolledBackVersion}` +
78
+ ` flm=${this.readString('firstLoadMarked')}` +
79
+ ` uuidSet=${!!this.readString('uuid')}`,
80
+ );
81
+ }
82
+
83
+ /** 对外诊断入口,供 TurboModule 在 getConstants 等关键节点打印状态。 */
84
+ public logStateSnapshot(point: string): void {
85
+ this.trace(point);
86
+ }
87
+
48
88
  private initPreferences() {
49
89
  try {
50
90
  this.preferences = preferences.getPreferencesSync(this.context, {
@@ -60,11 +100,15 @@ export class UpdateContext {
60
100
  }
61
101
 
62
102
  public getPackageVersion(): string {
103
+ if (UpdateContext.cachedPackageVersion) {
104
+ return UpdateContext.cachedPackageVersion;
105
+ }
63
106
  try {
64
107
  const bundleInfo = bundleManager.getBundleInfoForSelfSync(
65
108
  this.getBundleFlags(),
66
109
  );
67
- return bundleInfo?.versionName || 'Unknown';
110
+ UpdateContext.cachedPackageVersion = bundleInfo?.versionName || 'Unknown';
111
+ return UpdateContext.cachedPackageVersion;
68
112
  } catch (error) {
69
113
  console.error('Failed to get bundle info:', error);
70
114
  return '';
@@ -72,6 +116,9 @@ export class UpdateContext {
72
116
  }
73
117
 
74
118
  public getBuildTime(): string {
119
+ if (UpdateContext.cachedBuildTime) {
120
+ return UpdateContext.cachedBuildTime;
121
+ }
75
122
  try {
76
123
  const content =
77
124
  this.context.resourceManager.getRawFileContentSync('meta.json');
@@ -79,9 +126,12 @@ export class UpdateContext {
79
126
  new util.TextDecoder().decodeToString(content),
80
127
  ) as Record<string, string | number | boolean | null | undefined>;
81
128
  if (metaData.pushy_build_time) {
82
- return String(metaData.pushy_build_time);
129
+ UpdateContext.cachedBuildTime = String(metaData.pushy_build_time);
130
+ return UpdateContext.cachedBuildTime;
83
131
  }
84
- } catch {}
132
+ } catch (error) {
133
+ console.error('Failed to read build time from raw file:', error);
134
+ }
85
135
  return '';
86
136
  }
87
137
 
@@ -244,6 +294,9 @@ export class UpdateContext {
244
294
  packageVersion: string,
245
295
  buildTime: string,
246
296
  ): void {
297
+ if (!packageVersion || !buildTime) {
298
+ return;
299
+ }
247
300
  const currentState = this.getStateSnapshot();
248
301
  const nextState = NativePatchCore.syncStateWithBinaryVersion(
249
302
  packageVersion,
@@ -254,9 +307,17 @@ export class UpdateContext {
254
307
  return;
255
308
  }
256
309
 
310
+ logger.info(
311
+ 'UpdateContext',
312
+ `binary version changed, resetting update state id=${this.instanceId}`,
313
+ );
257
314
  UpdateContext.ignoreRollback = false;
258
315
  this.cleanUp();
259
- this.persistState(nextState, { clearExisting: true });
316
+ // 仅重置状态机字段(currentVersion / lastVersion / firstTime / firstTimeOk /
317
+ // rolledBackVersion)。不再 clearExisting,避免连带清除 uuid / firstLoadMarked /
318
+ // hash_* 等与 binary 版本无关的 KV —— 它们在多实例场景下本就脆弱,连带清除会
319
+ // 让 getConstants() 永远读到空,从而 isFirstTime=false、markSuccess 永不执行。
320
+ this.persistState(nextState);
260
321
  }
261
322
 
262
323
  public setKv(key: string, value: string): void {
@@ -373,8 +434,10 @@ export class UpdateContext {
373
434
  throw Error(`Bundle version ${hash} not found.`);
374
435
  }
375
436
 
437
+ this.trace(`switchVersion:before ${hash}`);
376
438
  this.runStateOperation(STATE_OP_SWITCH_VERSION, hash);
377
439
  UpdateContext.ignoreRollback = false;
440
+ this.trace(`switchVersion:after ${hash}`);
378
441
  } catch (e) {
379
442
  console.error('Failed to switch version:', e);
380
443
  throw e;
@@ -383,6 +446,7 @@ export class UpdateContext {
383
446
 
384
447
  public consumeFirstLoadMarker(): boolean {
385
448
  const marked = this.readString('firstLoadMarked') === 'true';
449
+ this.trace(`consumeFirstLoadMarker:marked=${marked}`);
386
450
  if (marked) {
387
451
  this.preferences.deleteSync('firstLoadMarked');
388
452
  this.flushPreferences('clear first load marker');
@@ -392,6 +456,7 @@ export class UpdateContext {
392
456
 
393
457
  public getBundleUrl() {
394
458
  UpdateContext.isUsingBundleUrl = true;
459
+ this.trace('getBundleUrl:enter');
395
460
  const launchState = NativePatchCore.runStateCore(
396
461
  STATE_OP_RESOLVE_LAUNCH,
397
462
  this.getStateSnapshot(),
@@ -407,6 +472,11 @@ export class UpdateContext {
407
472
  if (launchState.consumedFirstTime) {
408
473
  UpdateContext.ignoreRollback = true;
409
474
  }
475
+ this.trace(
476
+ `getBundleUrl:load=${launchState.loadVersion}` +
477
+ ` consumed=${launchState.consumedFirstTime}` +
478
+ ` rollback=${launchState.didRollback}`,
479
+ );
410
480
 
411
481
  let version = launchState.loadVersion || '';
412
482
  while (version) {
@@ -427,7 +497,9 @@ export class UpdateContext {
427
497
  }
428
498
 
429
499
  public getCurrentVersion(): string {
430
- return this.getStateSnapshot().currentVersion || '';
500
+ const cv = this.getStateSnapshot().currentVersion || '';
501
+ this.trace(`getCurrentVersion:${cv}`);
502
+ return cv;
431
503
  }
432
504
 
433
505
  private rollBack(): string {
package/harmony/pushy.har CHANGED
Binary file
@@ -0,0 +1,2 @@
1
+ @import React;
2
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.42.6",
3
+ "version": "10.42.9",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -104,6 +104,7 @@ Pod::Spec.new do |s|
104
104
 
105
105
  # Conditionally add Expo dependency
106
106
  if valid_expo_project
107
+ s.public_header_files = ['ios/ImportReact.h']
107
108
  s.dependency 'ExpoModulesCore'
108
109
  end
109
110
 
@@ -8,6 +8,12 @@ function normalizeVersion(version: string): string {
8
8
  return version.trim().replace(/^refs\/tags\//, '').replace(/^v/, '');
9
9
  }
10
10
 
11
+ const SEMVER_REGEX = /^\d+\.\d+\.\d+(-.+)?$/;
12
+
13
+ function isValidSemver(version: string): boolean {
14
+ return SEMVER_REGEX.test(version);
15
+ }
16
+
11
17
  function getVersionFromEnvironment(): string | null {
12
18
  const candidates = [
13
19
  process.env.RELEASE_VERSION,
@@ -134,6 +140,13 @@ async function main(): Promise<void> {
134
140
  try {
135
141
  if (isGitHubCI()) {
136
142
  const version = await resolveVersion();
143
+ if (!isValidSemver(version)) {
144
+ throw new Error(
145
+ `Resolved version "${version}" is not a valid semantic version. ` +
146
+ `Please ensure RELEASE_VERSION, GITHUB_REF, or GITHUB_REF_NAME is set correctly, ` +
147
+ `or that the git history is fully fetched.`
148
+ );
149
+ }
137
150
  console.log(`Using publish version ${version}`);
138
151
  await modifyPackageJson({ version });
139
152
  } else {
package/src/core.ts CHANGED
@@ -1,58 +1,59 @@
1
1
  import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
2
- import { emptyModule, error, log } from './utils';
3
2
  import i18n from './i18n';
3
+ import { emptyModule, error, log } from './utils';
4
+
4
5
  /* eslint-disable @react-native/no-deep-imports */
5
6
  const {
6
- version: v,
7
+ version: v,
7
8
  } = require('react-native/Libraries/Core/ReactNativeVersion');
8
9
  const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
9
10
  const isTurboModuleEnabled =
10
- // https://github.com/facebook/react-native/pull/48362
11
- (global as any).__turboModuleProxy || (global as any).RN$Bridgeless;
11
+ // https://github.com/facebook/react-native/pull/48362
12
+ (global as any).__turboModuleProxy || (global as any).RN$Bridgeless;
12
13
 
13
14
  export const PushyModule =
14
- Platform.OS === 'web'
15
- ? emptyModule
16
- : isTurboModuleEnabled
17
- ? require('./NativePushy').default
18
- : NativeModules.Pushy;
15
+ Platform.OS === 'web'
16
+ ? emptyModule
17
+ : isTurboModuleEnabled
18
+ ? require('./NativePushy').default
19
+ : NativeModules.Pushy;
19
20
 
20
21
  export const UpdateModule = PushyModule;
21
22
 
22
23
  if (!PushyModule) {
23
- throw Error(
24
- 'Failed to load react-native-update native module, please try to recompile',
25
- );
24
+ throw Error(
25
+ 'Failed to load react-native-update native module, please try to recompile',
26
+ );
26
27
  }
27
28
 
28
29
  const PushyConstants = isTurboModuleEnabled
29
- ? PushyModule.getConstants()
30
- : PushyModule;
30
+ ? PushyModule.getConstants()
31
+ : PushyModule;
31
32
 
32
33
  export const downloadRootDir: string = PushyConstants.downloadRootDir;
33
34
  export const packageVersion: string = PushyConstants.packageVersion;
34
35
  export const currentVersion: string = PushyConstants.currentVersion;
35
36
 
36
37
  export function setLocalHashInfo(hash: string, info: Record<string, any>) {
37
- PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
38
+ PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
38
39
  }
39
40
 
40
41
  const currentVersionInfoString: string = PushyConstants.currentVersionInfo;
41
42
  let _currentVersionInfo: Record<string, any> = {};
42
43
  let isDebugChannel = false;
43
44
  if (currentVersionInfoString) {
44
- try {
45
- _currentVersionInfo = JSON.parse(currentVersionInfoString);
46
- if (_currentVersionInfo.debugChannel) {
47
- isDebugChannel = true;
48
- delete _currentVersionInfo.debugChannel;
49
- setLocalHashInfo(currentVersion, _currentVersionInfo);
50
- }
51
- } catch {
52
- error(
53
- i18n.t('error_parse_version_info', { info: currentVersionInfoString }),
54
- );
55
- }
45
+ try {
46
+ _currentVersionInfo = JSON.parse(currentVersionInfoString);
47
+ if (_currentVersionInfo.debugChannel) {
48
+ isDebugChannel = true;
49
+ delete _currentVersionInfo.debugChannel;
50
+ setLocalHashInfo(currentVersion, _currentVersionInfo);
51
+ }
52
+ } catch {
53
+ error(
54
+ i18n.t('error_parse_version_info', { info: currentVersionInfoString }),
55
+ );
56
+ }
56
57
  }
57
58
  export const currentVersionInfo = _currentVersionInfo;
58
59
 
@@ -64,32 +65,39 @@ export const isRolledBack: boolean = !!rolledBackVersion;
64
65
  export const buildTime: string = PushyConstants.buildTime;
65
66
  let uuid = PushyConstants.uuid;
66
67
 
67
-
68
68
  async function getLocalHashInfo(hash: string) {
69
- return JSON.parse(await PushyModule.getLocalHashInfo(hash));
69
+ return JSON.parse(await PushyModule.getLocalHashInfo(hash));
70
70
  }
71
71
 
72
72
  // @deprecated use currentVersionInfo instead
73
73
  export async function getCurrentVersionInfo(): Promise<{
74
- name?: string;
75
- description?: string;
76
- metaInfo?: string;
74
+ name?: string;
75
+ description?: string;
76
+ metaInfo?: string;
77
77
  }> {
78
- return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
78
+ return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
79
79
  }
80
80
 
81
81
  export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
82
82
 
83
83
  if (!uuid) {
84
- uuid = require('nanoid/non-secure').nanoid();
85
- PushyModule.setUuid(uuid);
84
+ uuid = require('nanoid/non-secure').nanoid();
85
+ PushyModule.setUuid(uuid);
86
86
  }
87
87
 
88
- log('uuid: ' + uuid);
89
-
90
88
  export const cInfo = {
91
- rnu: require('../package.json').version,
92
- rn: RNVersion,
93
- os: Platform.OS + ' ' + Platform.Version,
94
- uuid,
89
+ rnu: require('../package.json').version,
90
+ rn: RNVersion,
91
+ os: `${Platform.OS} ${Platform.Version}`,
92
+ uuid,
95
93
  };
94
+
95
+ log('bootup status', {
96
+ packageVersion,
97
+ currentVersion,
98
+ currentVersionInfo,
99
+ isFirstTime,
100
+ isFirstTimeDebug,
101
+ isDebugChannel,
102
+ cInfo,
103
+ });