From d6a460c93112da04dc2630cebcf4b115d0dffc9a Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 29 Jun 2026 18:34:35 +0800 Subject: [PATCH 1/4] fix(harmony): align isFirstTime and rollback mechanism with android/ios --- .../pushy/src/main/ets/PushyTurboModule.ts | 2 +- harmony/pushy/src/main/ets/UpdateContext.ts | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/harmony/pushy/src/main/ets/PushyTurboModule.ts b/harmony/pushy/src/main/ets/PushyTurboModule.ts index 421b5c18..ee71638a 100644 --- a/harmony/pushy/src/main/ets/PushyTurboModule.ts +++ b/harmony/pushy/src/main/ets/PushyTurboModule.ts @@ -75,7 +75,7 @@ export class PushyTurboModule extends UITurboModule { const currentVersionInfo = currentVersion ? this.context.getKv(`hash_${currentVersion}`) : ''; - const isFirstTime = this.context.isFirstTime(); + const isFirstTime = this.context.consumeFirstLoadMarker(); const rolledBackVersion = this.context.rolledBackVersion(); const uuid = this.context.getKv('uuid'); const isUsingBundleUrl = this.context.getIsUsingBundleUrl(); diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index 9a96c52e..fb6adf59 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -25,6 +25,7 @@ export class UpdateContext { private preferences!: preferences.Preferences; private static DEBUG: boolean = false; private static isUsingBundleUrl: boolean = false; + private static ignoreRollback: boolean = false; constructor(context: common.UIAbilityContext) { this.context = context; @@ -245,6 +246,7 @@ export class UpdateContext { return; } + UpdateContext.ignoreRollback = false; this.cleanUp(); this.persistState(nextState, { clearExisting: true }); } @@ -361,23 +363,38 @@ export class UpdateContext { } this.runStateOperation(STATE_OP_SWITCH_VERSION, hash); + UpdateContext.ignoreRollback = false; } catch (e) { console.error('Failed to switch version:', e); throw e; } } + public consumeFirstLoadMarker(): boolean { + const marked = this.readString('firstLoadMarked') === 'true'; + if (marked) { + this.setKv('firstLoadMarked', 'false'); + } + return marked; + } + public getBundleUrl() { UpdateContext.isUsingBundleUrl = true; const launchState = NativePatchCore.runStateCore( STATE_OP_RESOLVE_LAUNCH, this.getStateSnapshot(), '', - false, - false, + UpdateContext.ignoreRollback, + true, ); if (launchState.didRollback || launchState.consumedFirstTime) { this.persistState(launchState); + if (launchState.consumedFirstTime) { + this.setKv('firstLoadMarked', 'true'); + } + } + if (launchState.consumedFirstTime) { + UpdateContext.ignoreRollback = true; } let version = launchState.loadVersion || ''; From b2a79e1994a7ed3e8934e0638231b1b1988db562 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 29 Jun 2026 18:53:49 +0800 Subject: [PATCH 2/4] cleanup --- harmony/pushy/src/main/ets/UpdateContext.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index fb6adf59..82433a24 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -281,6 +281,8 @@ export class UpdateContext { public clearFirstTime(): void { this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', { cleanUp: true }); + this.preferences.deleteSync('firstLoadMarked'); + this.flushPreferences('clear first time'); } public clearRollbackMark(): void { @@ -373,7 +375,8 @@ export class UpdateContext { public consumeFirstLoadMarker(): boolean { const marked = this.readString('firstLoadMarked') === 'true'; if (marked) { - this.setKv('firstLoadMarked', 'false'); + this.preferences.deleteSync('firstLoadMarked'); + this.flushPreferences('clear first load marker'); } return marked; } From d36a66b6d25a5cdac24ece82cf09fc7aad913b37 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 29 Jun 2026 19:00:58 +0800 Subject: [PATCH 3/4] fix(harmony): batch first-load marker persistence --- harmony/pushy/src/main/ets/UpdateContext.ts | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index 82433a24..7d70b4a7 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -73,9 +73,8 @@ export class UpdateContext { public getBuildTime(): string { try { - const content = this.context.resourceManager.getRawFileContentSync( - 'meta.json', - ); + const content = + this.context.resourceManager.getRawFileContentSync('meta.json'); const metaData = JSON.parse( new util.TextDecoder().decodeToString(content), ) as Record; @@ -183,6 +182,8 @@ export class UpdateContext { clearExisting?: boolean; removeStaleHash?: boolean; cleanUp?: boolean; + markFirstLoadMarker?: boolean; + clearFirstLoadMarker?: boolean; } = {}, ): void { if (options.clearExisting) { @@ -192,6 +193,12 @@ export class UpdateContext { if (options.removeStaleHash && state.staleVersionToDelete) { this.preferences.deleteSync(`hash_${state.staleVersionToDelete}`); } + if (options.markFirstLoadMarker) { + this.preferences.putSync('firstLoadMarked', 'true'); + } + if (options.clearFirstLoadMarker) { + this.preferences.deleteSync('firstLoadMarked'); + } this.flushPreferences('persist state'); if (options.cleanUp) { this.cleanUp(); @@ -204,6 +211,7 @@ export class UpdateContext { options: { removeStaleHash?: boolean; cleanUp?: boolean; + clearFirstLoadMarker?: boolean; } = {}, ): StateCoreResult { const nextState = NativePatchCore.runStateCore( @@ -280,9 +288,10 @@ export class UpdateContext { } public clearFirstTime(): void { - this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', { cleanUp: true }); - this.preferences.deleteSync('firstLoadMarked'); - this.flushPreferences('clear first time'); + this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', { + cleanUp: true, + clearFirstLoadMarker: true, + }); } public clearRollbackMark(): void { @@ -391,10 +400,9 @@ export class UpdateContext { true, ); if (launchState.didRollback || launchState.consumedFirstTime) { - this.persistState(launchState); - if (launchState.consumedFirstTime) { - this.setKv('firstLoadMarked', 'true'); - } + this.persistState(launchState, { + markFirstLoadMarker: launchState.consumedFirstTime, + }); } if (launchState.consumedFirstTime) { UpdateContext.ignoreRollback = true; From 5b691c4f6531ebd39664a95bac1c52a199b29ae2 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 30 Jun 2026 11:33:34 +0800 Subject: [PATCH 4/4] feat(harmony): implement in-memory soft reload using devToolsController RELOAD --- .../src/main/ets/PushyFileJSBundleProvider.ets | 15 +++++++-------- harmony/pushy/src/main/ets/PushyTurboModule.ts | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets b/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets index 06c9a172..d15f2407 100644 --- a/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +++ b/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets @@ -9,36 +9,35 @@ import { UpdateContext } from './UpdateContext'; export class PushyFileJSBundleProvider extends JSBundleProvider { private updateContext: UpdateContext; - private path: string = ''; constructor(context: common.UIAbilityContext) { super(); this.updateContext = new UpdateContext(context); - this.path = this.updateContext.getBundleUrl(); } getURL(): string { - return this.path; + return this.updateContext.getBundleUrl(); } async getBundle(): Promise { - if (!this.path) { + const path = this.updateContext.getBundleUrl(); + if (!path) { throw new JSBundleProviderError({ whatHappened: 'No pushy bundle found. using default bundle', howCanItBeFixed: [''], }); } try { - await fs.access(this.path, fs.OpenMode.READ_ONLY); + await fs.access(path, fs.OpenMode.READ_ONLY); return { - filePath: this.path, + filePath: path, }; } catch (error) { throw new JSBundleProviderError({ - whatHappened: `Couldn't load JSBundle from ${this.path}`, + whatHappened: `Couldn't load JSBundle from ${path}`, extraData: error, howCanItBeFixed: [ - `Check if a bundle exists at "${this.path}" on your device.`, + `Check if a bundle exists at "${path}" on your device.`, ], }); } diff --git a/harmony/pushy/src/main/ets/PushyTurboModule.ts b/harmony/pushy/src/main/ets/PushyTurboModule.ts index ee71638a..d4a6049f 100644 --- a/harmony/pushy/src/main/ets/PushyTurboModule.ts +++ b/harmony/pushy/src/main/ets/PushyTurboModule.ts @@ -65,6 +65,17 @@ export class PushyTurboModule extends UITurboModule { await this.mUiCtx.startAbility(want); } + private async reloadBridge(): Promise { + const devToolsController = (this.ctx as Record).devToolsController; + if (devToolsController) { + logger.debug(TAG, 'reloadBridge via devToolsController RELOAD'); + devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' }); + } else { + logger.debug(TAG, 'reloadBridge via restartAbility'); + await this.restartAbility(); + } + } + getConstants(): Object { logger.debug(TAG, ',call getConstants'); const packageVersion = this.context.getPackageVersion(); @@ -121,7 +132,7 @@ export class PushyTurboModule extends UITurboModule { try { this.context.switchVersion(hash); - await this.restartAbility(); + await this.reloadBridge(); } catch (error) { logger.error(TAG, `reloadUpdate failed: ${getErrorMessage(error)}`); throw Error(`switchVersion failed ${getErrorMessage(error)}`); @@ -131,7 +142,7 @@ export class PushyTurboModule extends UITurboModule { async restartApp(): Promise { logger.debug(TAG, ',call restartApp'); try { - await this.restartAbility(); + await this.reloadBridge(); } catch (error) { logger.error(TAG, `restartApp failed: ${getErrorMessage(error)}`); throw Error(`restartApp failed ${getErrorMessage(error)}`);