From 9f3ee25e945b0ce9d9c6d00694eab54fe982c5e8 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 17:48:01 +0800 Subject: [PATCH 01/29] refactor: extract the pure update-flow decision layer into updateFlowCore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildCheckRequestBody, resolveCheckResult, decideDownload, isInRollout, joinUrls and orderEndpointCandidates now live in src/updateFlowCore.ts with a pure import closure — no IO, no react-native, identity and randomness injected as parameters — so the exact same source can later be evaluated by the native orchestrator (NATIVE_CHECKUPDATE_DESIGN §5). client.ts, provider.tsx and endpoint.ts consume it in place; the endpoint candidate ordering (random first pick, configured order as fallback) moves into the pure layer while the hedged-race engine stays IO-side. Behavior is unchanged; isInRollout tests no longer need module mocks. Co-Authored-By: Claude Fable 5 --- src/__tests__/isInRollout.test.ts | 80 ++----- src/__tests__/resolveCheckResult.test.ts | 52 ++++- src/__tests__/setup.ts | 3 +- src/__tests__/updateFlowCore.test.ts | 227 ++++++++++++++++++ src/client.ts | 198 +++++++--------- src/endpoint.ts | 36 +-- src/isInRollout.ts | 87 ------- src/provider.tsx | 9 +- src/resolveCheckResult.ts | 30 --- src/updateFlowCore.ts | 281 +++++++++++++++++++++++ src/utils.ts | 13 +- 11 files changed, 667 insertions(+), 349 deletions(-) create mode 100644 src/__tests__/updateFlowCore.test.ts delete mode 100644 src/isInRollout.ts delete mode 100644 src/resolveCheckResult.ts create mode 100644 src/updateFlowCore.ts diff --git a/src/__tests__/isInRollout.test.ts b/src/__tests__/isInRollout.test.ts index de61118e..26e2c928 100644 --- a/src/__tests__/isInRollout.test.ts +++ b/src/__tests__/isInRollout.test.ts @@ -1,30 +1,8 @@ -import { beforeEach, describe, expect, it, mock } from 'bun:test'; +import { describe, expect, it } from 'bun:test'; -// Use the preload setup file instead of inline mocks since bun resolves -// dynamic imports relative to the test runner's context and caching. import './setup'; -let mockUuid = ''; -// Installed per test: setup.ts hands every module back to its real -// implementation after each test, so a file-scope mock would only survive the -// first one. -beforeEach(() => { - mock.module('../core', () => { - return { - cInfo: { - get uuid() { - return mockUuid; - }, - }, - }; - }); -}); - -// Use a monotonic counter instead of Date.now() to avoid cache collisions -// when two dynamic imports happen within the same millisecond. -let importCounter = 0; - -import { murmurhash3_32_gc } from '../isInRollout'; +import { isInRollout, murmurhash3_32_gc } from '../updateFlowCore'; describe('murmurhash3_32_gc', () => { it('should be deterministic (return the same output for the same input)', () => { @@ -72,53 +50,31 @@ describe('murmurhash3_32_gc', () => { }); describe('isInRollout', () => { - it('should return true when the rollout is greater than the hash modulo', async () => { - mockUuid = 'test1'; // hash % 100 === 62 - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(63)).toBe(true); + it('should return true when the rollout is greater than the hash modulo', () => { + // murmur('test1') % 100 === 62 + expect(isInRollout(63, 'test1')).toBe(true); }); - it('should return false when the rollout is equal to the hash modulo', async () => { - mockUuid = 'test1'; - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(62)).toBe(false); + it('should return false when the rollout is equal to the hash modulo', () => { + expect(isInRollout(62, 'test1')).toBe(false); }); - it('should return false when the rollout is less than the hash modulo', async () => { - mockUuid = 'test1'; - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(61)).toBe(false); + it('should return false when the rollout is less than the hash modulo', () => { + expect(isInRollout(61, 'test1')).toBe(false); }); - it('should evaluate correctly for a different uuid', async () => { - mockUuid = 'test3'; // hash % 100 === 53 - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(54)).toBe(true); - expect(isInRollout(53)).toBe(false); - expect(isInRollout(-1)).toBe(false); + it('should evaluate correctly for a different uuid', () => { + // murmur('test3') % 100 === 53 + expect(isInRollout(54, 'test3')).toBe(true); + expect(isInRollout(53, 'test3')).toBe(false); + expect(isInRollout(-1, 'test3')).toBe(false); }); - it('should always return false for 0% rollout', async () => { - mockUuid = 'test1'; - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(0)).toBe(false); + it('should always return false for 0% rollout', () => { + expect(isInRollout(0, 'test1')).toBe(false); }); - it('should always return true for 100% rollout', async () => { - mockUuid = 'test1'; - const { isInRollout } = await import( - `../isInRollout?id=${++importCounter}` - ); - expect(isInRollout(100)).toBe(true); + it('should always return true for 100% rollout', () => { + expect(isInRollout(100, 'test1')).toBe(true); }); }); diff --git a/src/__tests__/resolveCheckResult.test.ts b/src/__tests__/resolveCheckResult.test.ts index 0f603790..208c963a 100644 --- a/src/__tests__/resolveCheckResult.test.ts +++ b/src/__tests__/resolveCheckResult.test.ts @@ -1,7 +1,12 @@ import { describe, expect, test } from 'bun:test'; -import { currentVersion, packageVersion } from '../core'; -import { resolveCheckResult } from '../resolveCheckResult'; import type { CheckResult } from '../type'; +import { resolveCheckResult, type UpdateIdentity } from '../updateFlowCore'; + +const identity: UpdateIdentity = { + packageVersion: '1.0.0', + currentVersion: 'current-hash', + uuid: 'any-uuid', +}; const createRootResult = ( overrides: Partial = {} @@ -24,16 +29,17 @@ describe('resolveCheckResult', () => { createRootResult({ expVersion: { name: 'gray-current', - hash: currentVersion, + hash: identity.currentVersion!, description: 'gray description', metaInfo: 'gray meta', config: { rollout: { - [packageVersion]: 100, + [identity.packageVersion]: 100, }, }, }, - }) + }), + identity ); expect(result).toEqual({ upToDate: true }); @@ -49,11 +55,12 @@ describe('resolveCheckResult', () => { metaInfo: 'gray meta', config: { rollout: { - [packageVersion]: 100, + [identity.packageVersion]: 100, }, }, }, - }) + }), + identity ); expect(result).toEqual({ @@ -64,7 +71,7 @@ describe('resolveCheckResult', () => { metaInfo: 'gray meta', config: { rollout: { - [packageVersion]: 100, + [identity.packageVersion]: 100, }, }, paths: ['cdn.example.com'], @@ -81,11 +88,12 @@ describe('resolveCheckResult', () => { metaInfo: 'gray meta', config: { rollout: { - [packageVersion]: 0, + [identity.packageVersion]: 0, }, }, }, - }) + }), + identity ); expect(result).toEqual(createRootResult()); @@ -93,9 +101,31 @@ describe('resolveCheckResult', () => { test('returns upToDate when root target is already current', () => { const result = resolveCheckResult( - createRootResult({ hash: currentVersion }) + createRootResult({ hash: identity.currentVersion }), + identity ); expect(result).toEqual({ upToDate: true }); }); + + test('ignores rollout config for a different packageVersion', () => { + const result = resolveCheckResult( + createRootResult({ + expVersion: { + name: 'gray-next', + hash: 'gray-hash', + description: 'gray description', + metaInfo: 'gray meta', + config: { + rollout: { + 'some-other-package': 100, + }, + }, + }, + }), + identity + ); + + expect(result).toEqual(createRootResult()); + }); }); diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts index 0f534b4a..79d5e2b0 100644 --- a/src/__tests__/setup.ts +++ b/src/__tests__/setup.ts @@ -96,10 +96,9 @@ const realProjectModules: Record> = { '../core': { ...(await import('../core')) }, '../endpoint': { ...(await import('../endpoint')) }, '../error': { ...(await import('../error')) }, - '../isInRollout': { ...(await import('../isInRollout')) }, '../permissions': { ...(await import('../permissions')) }, - '../resolveCheckResult': { ...(await import('../resolveCheckResult')) }, '../telemetry': { ...(await import('../telemetry')) }, + '../updateFlowCore': { ...(await import('../updateFlowCore')) }, '../utils': { ...(await import('../utils')) }, }; diff --git a/src/__tests__/updateFlowCore.test.ts b/src/__tests__/updateFlowCore.test.ts new file mode 100644 index 00000000..d358b9e3 --- /dev/null +++ b/src/__tests__/updateFlowCore.test.ts @@ -0,0 +1,227 @@ +import { describe, expect, test } from 'bun:test'; +import type { CheckResult } from '../type'; +import { + buildCheckRequestBody, + decideDownload, + orderEndpointCandidates, +} from '../updateFlowCore'; + +const cInfo = { rnu: '10.50.0', rn: '0.73.0', os: 'ios 17', uuid: 'uuid' }; + +const baseInput = { + packageVersion: '1.0.0', + currentVersion: 'current-hash', + buildTime: '2023-01-01', + cInfo, +}; + +describe('buildCheckRequestBody', () => { + test('builds the base body', () => { + expect(buildCheckRequestBody(baseInput)).toEqual({ + packageVersion: '1.0.0', + hash: 'current-hash', + buildTime: '2023-01-01', + cInfo, + }); + }); + + test('includes diffV and bundleHash when known', () => { + expect( + buildCheckRequestBody({ + ...baseInput, + supportedDiffVersion: 2, + bundleHash: 'a'.repeat(64), + }) + ).toEqual({ + packageVersion: '1.0.0', + hash: 'current-hash', + buildTime: '2023-01-01', + cInfo, + diffV: 2, + bundleHash: 'a'.repeat(64), + }); + }); + + test('omits diffV and bundleHash while unknown', () => { + const body = buildCheckRequestBody({ + ...baseInput, + supportedDiffVersion: 0, + bundleHash: '', + }); + expect(body).not.toHaveProperty('diffV'); + expect(body).not.toHaveProperty('bundleHash'); + }); + + test('spreads extra last so it can override fields', () => { + const body = buildCheckRequestBody({ + ...baseInput, + extra: { toHash: 'debug-hash', hash: 'override-hash' }, + }); + expect(body.toHash).toBe('debug-hash'); + expect(body.hash).toBe('override-hash'); + }); + + test('drops buildTime in dev, even when set via extra', () => { + const body = buildCheckRequestBody({ + ...baseInput, + isDev: true, + extra: { buildTime: 'injected' }, + }); + expect(body).not.toHaveProperty('buildTime'); + }); +}); + +describe('orderEndpointCandidates', () => { + test('keeps configured order when the sample picks the first', () => { + expect(orderEndpointCandidates(['a', 'b', 'c'], 0)).toEqual([ + 'a', + 'b', + 'c', + ]); + }); + + test('moves the sampled pick to the front, rest in configured order', () => { + expect(orderEndpointCandidates(['a', 'b', 'c'], 0.5)).toEqual([ + 'b', + 'a', + 'c', + ]); + expect(orderEndpointCandidates(['a', 'b', 'c'], 0.99)).toEqual([ + 'c', + 'a', + 'b', + ]); + }); + + test('clamps an out-of-range sample to the last candidate', () => { + expect(orderEndpointCandidates(['a', 'b', 'c'], 1)).toEqual([ + 'c', + 'a', + 'b', + ]); + }); + + test('dedupes and drops empty entries before ordering', () => { + expect(orderEndpointCandidates(['a', null, 'a', '', 'b'], 0)).toEqual([ + 'a', + 'b', + ]); + }); + + test('passes through empty and single-candidate lists', () => { + expect(orderEndpointCandidates([], 0.5)).toEqual([]); + expect(orderEndpointCandidates(['a'], 0.5)).toEqual(['a']); + }); +}); + +describe('decideDownload', () => { + const identity = { + currentVersion: 'current-hash', + rolledBackVersion: 'bad-hash', + }; + + const updateInfo = (overrides: Partial = {}): CheckResult => ({ + update: true, + hash: 'next-hash', + diff: 'current-next.hdiff', + pdiff: 'package-next.phdiff', + full: 'next-hash.ppk', + paths: ['cdn.example.com', 'https://mirror.example.com'], + ...overrides, + }); + + test('declines when there is no update or no hash', () => { + expect(decideDownload({ upToDate: true }, identity)).toEqual({ + action: 'none', + reason: 'noUpdate', + }); + expect(decideDownload({ update: true }, identity)).toEqual({ + action: 'none', + reason: 'noUpdate', + }); + }); + + test('declines the currently running version', () => { + expect( + decideDownload(updateInfo({ hash: 'current-hash' }), identity) + ).toEqual({ action: 'none', reason: 'alreadyCurrent' }); + }); + + test('declines a rolled-back version', () => { + expect(decideDownload(updateInfo({ hash: 'bad-hash' }), identity)).toEqual({ + action: 'none', + reason: 'rolledBack', + }); + }); + + test('orders attempts diff → pdiff → full with joined candidate urls', () => { + const decision = decideDownload(updateInfo(), identity); + expect(decision).toEqual({ + action: 'download', + hash: 'next-hash', + devNoop: false, + attempts: [ + { + type: 'diff', + urls: [ + 'https://cdn.example.com/current-next.hdiff', + 'https://mirror.example.com/current-next.hdiff', + ], + }, + { + type: 'pdiff', + urls: [ + 'https://cdn.example.com/package-next.phdiff', + 'https://mirror.example.com/package-next.phdiff', + ], + }, + { + type: 'full', + urls: [ + 'https://cdn.example.com/next-hash.ppk', + 'https://mirror.example.com/next-hash.ppk', + ], + }, + ], + }); + }); + + test('skips artifacts the server did not offer', () => { + const decision = decideDownload(updateInfo({ diff: undefined }), identity); + if (decision.action !== 'download') { + throw new Error('expected a download decision'); + } + expect(decision.attempts.map((a) => a.type)).toEqual(['pdiff', 'full']); + }); + + test('yields no attempts when paths are empty', () => { + const decision = decideDownload(updateInfo({ paths: [] }), identity); + if (decision.action !== 'download') { + throw new Error('expected a download decision'); + } + expect(decision.attempts).toEqual([]); + expect(decision.devNoop).toBe(false); + }); + + test('dev only attempts full', () => { + const decision = decideDownload(updateInfo(), identity, true); + if (decision.action !== 'download') { + throw new Error('expected a download decision'); + } + expect(decision.attempts.map((a) => a.type)).toEqual(['full']); + expect(decision.devNoop).toBe(false); + }); + + test('dev with no full artifact is a no-op success', () => { + const decision = decideDownload( + updateInfo({ full: undefined }), + identity, + true + ); + if (decision.action !== 'download') { + throw new Error('expected a download decision'); + } + expect(decision.attempts).toEqual([]); + expect(decision.devNoop).toBe(true); + }); +}); diff --git a/src/client.ts b/src/client.ts index 871023de..5610a4de 100644 --- a/src/client.ts +++ b/src/client.ts @@ -41,13 +41,18 @@ import type { UpdateCheckState, UpdateServerConfig, } from './type'; +import { + buildCheckRequestBody, + type DownloadPlan, + type DownloadStrategyType, + decideDownload, +} from './updateFlowCore'; import { assertWeb, computeProgress, DEFAULT_FETCH_TIMEOUT_MS, fetchWithTimeout, info, - joinUrls, log, noop, promiseAny, @@ -697,19 +702,16 @@ export class Pushy { } } this.lastChecking = now; - const fetchBody: Record = { + const fetchBody = buildCheckRequestBody({ packageVersion: this.options.overridePackageVersion || packageVersion, - hash: currentVersion, + currentVersion, buildTime, cInfo, - // 可消费的 diff 轨道版本(2 = hdiffv2 轨道),服务端据此门控下发 - ...(supportedDiffVersion ? { diffV: supportedDiffVersion } : {}), - ...(bundleHash ? { bundleHash } : {}), - ...extra, - }; - if (__DEV__) { - delete fetchBody.buildTime; - } + supportedDiffVersion, + bundleHash, + isDev: __DEV__, + extra, + }); const stringifyBody = JSON.stringify(fetchBody); // harmony fetch body is not string let body: any = fetchBody; @@ -766,7 +768,6 @@ export class Pushy { updateInfo: CheckResult, onDownloadProgress?: (data: ProgressData) => void ) => { - const { hash } = updateInfo; if ( this.options.beforeDownloadUpdate && (await this.options.beforeDownloadUpdate(updateInfo)) === false @@ -774,17 +775,20 @@ export class Pushy { log('beforeDownloadUpdate returned false, skipping download'); return; } - if (!updateInfo.update || !hash) { - return; - } - if (hash === currentVersion) { - log(`current hash ${currentVersion}, ignored`); - return; - } - if (rolledBackVersion === hash) { - log(`rolledback hash ${rolledBackVersion}, ignored`); + const decision = decideDownload( + updateInfo, + { currentVersion, rolledBackVersion }, + __DEV__ + ); + if (decision.action === 'none') { + if (decision.reason === 'alreadyCurrent') { + log(`current hash ${currentVersion}, ignored`); + } else if (decision.reason === 'rolledBack') { + log(`rolledback hash ${rolledBackVersion}, ignored`); + } return; } + const { hash } = decision; if (sharedState.downloadedHash === hash) { log(`duplicated downloaded hash ${sharedState.downloadedHash}, ignored`); return sharedState.downloadedHash; @@ -801,7 +805,7 @@ export class Pushy { } return existingTask; } - const task = this.performDownload(updateInfo, onDownloadProgress); + const task = this.performDownload(updateInfo, decision, onDownloadProgress); sharedState.downloadingTasks[hash] = task; try { return await task; @@ -811,21 +815,11 @@ export class Pushy { }; private performDownload = async ( updateInfo: CheckResult, + plan: DownloadPlan, onDownloadProgress?: (data: ProgressData) => void ) => { - const { - hash, - diff, - pdiff, - full, - paths = [], - name, - description = '', - metaInfo, - } = updateInfo; - if (!hash) { - return; - } + const { name, description = '', metaInfo } = updateInfo; + const { hash, attempts, devNoop } = plan; const patchStartTime = Date.now(); // One native listener per hash dispatching to a callback set, so // concurrent callers deduped onto this task can each observe progress @@ -870,61 +864,35 @@ export class Pushy { let lastError: any; const errorMessages: string[] = []; - // Ordered download strategies, tried in sequence until one succeeds. Each - // resolves its candidate URL lazily (testUrls) and runs the matching native - // download. diff/pdiff are incremental and skipped entirely in dev; full is - // attempted whenever a URL exists, and in dev with no URL it is treated as a - // no-op success so the flow can proceed. - type DownloadStrategy = { - name: string; - candidate: string | undefined; - errorKey: - | 'error_diff_failed' - | 'error_pdiff_failed' - | 'error_full_patch_failed'; - skipInDev: boolean; - devNoopWhenNoUrl: boolean; - run: (url: string) => Promise; + // The ordered attempts come from decideDownload (the pure decision layer); + // this side only executes them: probe candidate URLs, run the matching + // native download, fall through to the next attempt on failure. + const runners: Record< + DownloadStrategyType, + (url: string) => Promise + > = { + diff: (url) => + PushyModule.downloadPatchFromPpk({ + updateUrl: url, + hash, + originHash: currentVersion, + }), + pdiff: (url) => + PushyModule.downloadPatchFromPackage({ + updateUrl: url, + hash, + }), + full: (url) => + PushyModule.downloadFullUpdate({ + updateUrl: url, + hash, + }), }; - const strategies: DownloadStrategy[] = [ - { - name: 'diff', - candidate: diff, - errorKey: 'error_diff_failed', - skipInDev: true, - devNoopWhenNoUrl: false, - run: (url) => - PushyModule.downloadPatchFromPpk({ - updateUrl: url, - hash, - originHash: currentVersion, - }), - }, - { - name: 'pdiff', - candidate: pdiff, - errorKey: 'error_pdiff_failed', - skipInDev: true, - devNoopWhenNoUrl: false, - run: (url) => - PushyModule.downloadPatchFromPackage({ - updateUrl: url, - hash, - }), - }, - { - name: 'full', - candidate: full, - errorKey: 'error_full_patch_failed', - skipInDev: false, - devNoopWhenNoUrl: true, - run: (url) => - PushyModule.downloadFullUpdate({ - updateUrl: url, - hash, - }), - }, - ]; + const errorKeys = { + diff: 'error_diff_failed', + pdiff: 'error_pdiff_failed', + full: 'error_full_patch_failed', + } as const; for (let attempt = 0; attempt <= maxRetries; attempt++) { if (attempt > 0) { @@ -942,34 +910,36 @@ export class Pushy { attempt, }, }); - for (const strategy of strategies) { + if (devNoop) { + log(this.t('dev_incremental_update_disabled')); + succeeded = 'full'; + } + for (const { type, urls } of attempts) { if (succeeded) { break; } - const url = await testUrls(joinUrls(paths, strategy.candidate)); - if (url && !(strategy.skipInDev && __DEV__)) { - log(`downloading ${strategy.name}`); - try { - await strategy.run(url); - succeeded = strategy.name; - } catch (e: any) { - const errorMessage = this.t(strategy.errorKey, { - message: e.message, - }); - errorMessages.push(errorMessage); - // Keep the i18n message for display, but preserve the native - // rejection's stable code (e.g. PATCH_FAILED vs DOWNLOAD_FAILED — - // telemetry classifies on it) and the original error as cause. - lastError = new UpdateError( - errorMessage, - asUpdateErrorCode(e?.code) ?? 'DOWNLOAD_FAILED', - { cause: e } - ); - log(errorMessage); - } - } else if (!url && strategy.devNoopWhenNoUrl && __DEV__) { - log(this.t('dev_incremental_update_disabled')); - succeeded = strategy.name; + const url = await testUrls(urls); + if (!url) { + continue; + } + log(`downloading ${type}`); + try { + await runners[type](url); + succeeded = type; + } catch (e: any) { + const errorMessage = this.t(errorKeys[type], { + message: e.message, + }); + errorMessages.push(errorMessage); + // Keep the i18n message for display, but preserve the native + // rejection's stable code (e.g. PATCH_FAILED vs DOWNLOAD_FAILED — + // telemetry classifies on it) and the original error as cause. + lastError = new UpdateError( + errorMessage, + asUpdateErrorCode(e?.code) ?? 'DOWNLOAD_FAILED', + { cause: e } + ); + log(errorMessage); } } if (succeeded) { diff --git a/src/endpoint.ts b/src/endpoint.ts index dca24a49..49c3c5d1 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -1,4 +1,7 @@ import { UpdateError } from './error'; +import { dedupeEndpoints, orderEndpointCandidates } from './updateFlowCore'; + +export { dedupeEndpoints }; export interface EndpointAttemptSuccess { endpoint: string; @@ -28,33 +31,6 @@ const normalizeError = (error: unknown) => { return new Error(String(error)); }; -export const dedupeEndpoints = ( - endpoints: Array -): string[] => { - const result: string[] = []; - const visited = new Set(); - - for (const endpoint of endpoints) { - if (!endpoint || visited.has(endpoint)) { - continue; - } - visited.add(endpoint); - result.push(endpoint); - } - - return result; -}; - -export const pickRandomEndpoint = ( - endpoints: string[], - random: () => number = Math.random -) => { - if (!endpoints.length) { - throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS'); - } - return endpoints[Math.floor(random() * endpoints.length)]; -}; - export const DEFAULT_HEDGE_DELAY_MS = 250; /** @@ -165,13 +141,15 @@ export async function executeEndpointFallback({ onFirstFailure, }: ExecuteEndpointFallbackOptions): Promise> { const excludedEndpoints = new Set(); - let candidates = dedupeEndpoints(configuredEndpoints); + // The candidate ordering (random first pick, configured order as fallback) + // is pure policy; this side only executes it. + let candidates = orderEndpointCandidates(configuredEndpoints, random()); if (!candidates.length) { throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS'); } - const firstEndpoint = pickRandomEndpoint(candidates, random); + const firstEndpoint = candidates[0]; try { return { diff --git a/src/isInRollout.ts b/src/isInRollout.ts deleted file mode 100644 index e3a38e46..00000000 --- a/src/isInRollout.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable no-fallthrough */ - -import { cInfo } from './core'; - -/* eslint-disable no-bitwise */ -export function murmurhash3_32_gc(key: string, seed = 0) { - let remainder: number, - bytes: number, - h1: number, - h1b: number, - c1: number, - c2: number, - k1: number, - i: number; - - remainder = key.length & 3; // key.length % 4 - bytes = key.length - remainder; - h1 = seed; - c1 = 0xcc9e2d51; - c2 = 0x1b873593; - i = 0; - - while (i < bytes) { - k1 = - (key.charCodeAt(i) & 0xff) | - ((key.charCodeAt(++i) & 0xff) << 8) | - ((key.charCodeAt(++i) & 0xff) << 16) | - ((key.charCodeAt(++i) & 0xff) << 24); - ++i; - - k1 = - ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = - ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1b = - ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff; - h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16); - } - - k1 = 0; - - switch (remainder) { - // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough - case 3: - k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; - // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough - case 2: - k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; - case 1: - k1 ^= key.charCodeAt(i) & 0xff; - - k1 = - ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & - 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = - ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & - 0xffffffff; - h1 ^= k1; - } - - h1 ^= key.length; - - h1 ^= h1 >>> 16; - h1 = - ((h1 & 0xffff) * 0x85ebca6b + - ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & - 0xffffffff; - h1 ^= h1 >>> 13; - h1 = - ((h1 & 0xffff) * 0xc2b2ae35 + - ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & - 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; -} - -const intForUUID = murmurhash3_32_gc(cInfo.uuid); - -export function isInRollout(rollout: number) { - return intForUUID % 100 < rollout; -} diff --git a/src/provider.tsx b/src/provider.tsx index 9ecfffc4..24bea63f 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -18,13 +18,14 @@ import { URL } from 'react-native-url-polyfill'; import { type Cresc, type Pushy, sharedState } from './client'; import { ProgressContext, UpdateContext } from './context'; import { + cInfo, currentVersion, currentVersionInfo, getCurrentVersionInfo, packageVersion, } from './core'; -import { resolveCheckResult } from './resolveCheckResult'; import type { CheckResult, ProgressData, UpdateTestPayload } from './type'; +import { resolveCheckResult } from './updateFlowCore'; import { assertWeb, log, noop } from './utils'; export const UpdateProvider = ({ @@ -222,7 +223,11 @@ export const UpdateProvider = ({ // known updateInfo instead of overwriting it with an empty object. return; } - const info = resolveCheckResult(rootInfo); + const info = resolveCheckResult( + rootInfo, + { packageVersion, currentVersion, uuid: cInfo.uuid }, + log + ); if (info.update) { info.description = info.description ?? ''; } diff --git a/src/resolveCheckResult.ts b/src/resolveCheckResult.ts deleted file mode 100644 index b44eb806..00000000 --- a/src/resolveCheckResult.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { currentVersion, packageVersion } from './core'; -import { isInRollout } from './isInRollout'; -import type { CheckResult } from './type'; -import { log } from './utils'; - -export function resolveCheckResult(rootInfo: CheckResult): CheckResult { - const { expVersion, ...rootResult } = rootInfo; - const rollout = expVersion?.config?.rollout?.[packageVersion]; - if (rootResult.update && expVersion && typeof rollout === 'number') { - if (isInRollout(rollout)) { - log(`${expVersion.name} in ${rollout}% rollout, continue`); - if (expVersion.hash === currentVersion) { - return { upToDate: true }; - } - const info: CheckResult = { - update: true, - ...expVersion, - }; - if (rootResult.paths) { - info.paths = rootResult.paths; - } - return info; - } - log(`${expVersion.name} not in ${rollout}% rollout, ignored`); - } - if (rootResult.update && rootResult.hash === currentVersion) { - return { upToDate: true }; - } - return rootResult; -} diff --git a/src/updateFlowCore.ts b/src/updateFlowCore.ts new file mode 100644 index 00000000..e4bdc528 --- /dev/null +++ b/src/updateFlowCore.ts @@ -0,0 +1,281 @@ +import type { CheckResult } from './type'; + +// 更新流程的纯决策层:无 IO、不依赖 react-native、无模块级状态,所有输入均由 +// 参数传入(仅允许 type-only import)。这一层必须保持可在裸 JS 引擎中求值—— +// 它就是未来 guardian bundle 的编译单元(NATIVE_CHECKUPDATE_DESIGN §5), +// IO(HTTP/下载/落盘)由调用方(现在是 client.ts,将来是原生编排)执行。 + +/* eslint-disable no-fallthrough */ +/* eslint-disable no-bitwise */ +export function murmurhash3_32_gc(key: string, seed = 0) { + let remainder: number, + bytes: number, + h1: number, + h1b: number, + c1: number, + c2: number, + k1: number, + i: number; + + remainder = key.length & 3; // key.length % 4 + bytes = key.length - remainder; + h1 = seed; + c1 = 0xcc9e2d51; + c2 = 0x1b873593; + i = 0; + + while (i < bytes) { + k1 = + (key.charCodeAt(i) & 0xff) | + ((key.charCodeAt(++i) & 0xff) << 8) | + ((key.charCodeAt(++i) & 0xff) << 16) | + ((key.charCodeAt(++i) & 0xff) << 24); + ++i; + + k1 = + ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = + ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1b = + ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff; + h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16); + } + + k1 = 0; + + switch (remainder) { + // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough + case 3: + k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough + case 2: + k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; + case 1: + k1 ^= key.charCodeAt(i) & 0xff; + + k1 = + ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & + 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = + ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & + 0xffffffff; + h1 ^= k1; + } + + h1 ^= key.length; + + h1 ^= h1 >>> 16; + h1 = + ((h1 & 0xffff) * 0x85ebca6b + + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & + 0xffffffff; + h1 ^= h1 >>> 13; + h1 = + ((h1 & 0xffff) * 0xc2b2ae35 + + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & + 0xffffffff; + h1 ^= h1 >>> 16; + + return h1 >>> 0; +} + +export function isInRollout(rollout: number, uuid: string) { + return murmurhash3_32_gc(uuid) % 100 < rollout; +} + +export function joinUrls(paths: string[], fileName?: string) { + if (fileName) { + return paths.map((path) => { + const normalizedPath = path.replace(/\/+$/, ''); + // Keep explicit http(s) URLs for local/self-hosted update sources. + const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(normalizedPath) + ? normalizedPath + : `https://${normalizedPath}`; + return `${baseUrl}/${fileName}`; + }); + } +} + +export const dedupeEndpoints = ( + endpoints: Array +): string[] => { + const result: string[] = []; + const visited = new Set(); + + for (const endpoint of endpoints) { + if (!endpoint || visited.has(endpoint)) { + continue; + } + visited.add(endpoint); + result.push(endpoint); + } + + return result; +}; + +/** + * The endpoint plan: dedupe, then move the sampled pick to the front (load + * spreading), keeping the rest in configured order as the fallback sequence. + * `randomSample` ∈ [0, 1) is injected by the caller — this layer cannot draw + * randomness itself. + */ +export function orderEndpointCandidates( + endpoints: Array, + randomSample = 0 +): string[] { + const deduped = dedupeEndpoints(endpoints); + if (deduped.length < 2) { + return deduped; + } + const first = Math.min( + Math.floor(randomSample * deduped.length), + deduped.length - 1 + ); + return [ + deduped[first], + ...deduped.slice(0, first), + ...deduped.slice(first + 1), + ]; +} + +export interface CheckRequestInput { + /** Effective native package version (overridePackageVersion already applied). */ + packageVersion: string; + /** Hash of the currently running JS version ('' when on the packaged bundle). */ + currentVersion?: string; + buildTime: string; + cInfo: Record; + supportedDiffVersion?: number; + /** '' or undefined while unknown — the field is then omitted. */ + bundleHash?: string; + isDev?: boolean; + extra?: Record; +} + +export function buildCheckRequestBody({ + packageVersion, + currentVersion, + buildTime, + cInfo, + supportedDiffVersion, + bundleHash, + isDev, + extra, +}: CheckRequestInput): Record { + const body: Record = { + packageVersion, + hash: currentVersion, + buildTime, + cInfo, + // 可消费的 diff 轨道版本(2 = hdiffv2 轨道),服务端据此门控下发 + ...(supportedDiffVersion ? { diffV: supportedDiffVersion } : {}), + ...(bundleHash ? { bundleHash } : {}), + ...extra, + }; + if (isDev) { + delete body.buildTime; + } + return body; +} + +export interface UpdateIdentity { + packageVersion: string; + currentVersion?: string; + /** Stable client uuid — the gray-release bucketing key. */ + uuid: string; +} + +export function resolveCheckResult( + rootInfo: CheckResult, + identity: UpdateIdentity, + log: (...args: any[]) => void = () => {} +): CheckResult { + const { expVersion, ...rootResult } = rootInfo; + const rollout = expVersion?.config?.rollout?.[identity.packageVersion]; + if (rootResult.update && expVersion && typeof rollout === 'number') { + if (isInRollout(rollout, identity.uuid)) { + log(`${expVersion.name} in ${rollout}% rollout, continue`); + if (expVersion.hash === identity.currentVersion) { + return { upToDate: true }; + } + const info: CheckResult = { + update: true, + ...expVersion, + }; + if (rootResult.paths) { + info.paths = rootResult.paths; + } + return info; + } + log(`${expVersion.name} not in ${rollout}% rollout, ignored`); + } + if (rootResult.update && rootResult.hash === identity.currentVersion) { + return { upToDate: true }; + } + return rootResult; +} + +export type DownloadStrategyType = 'diff' | 'pdiff' | 'full'; + +export interface DownloadAttempt { + type: DownloadStrategyType; + /** Candidate URLs (paths × file name); the executor probes and picks one. */ + urls: string[]; +} + +export interface DownloadPlan { + hash: string; + /** Ordered attempts: incremental first, full as the last resort. */ + attempts: DownloadAttempt[]; + /** Dev-only: nothing to fetch — treat as an immediate no-op success. */ + devNoop: boolean; +} + +export type DownloadDecision = + | { action: 'none'; reason: 'noUpdate' | 'alreadyCurrent' | 'rolledBack' } + | ({ action: 'download' } & DownloadPlan); + +export function decideDownload( + info: CheckResult, + identity: { currentVersion?: string; rolledBackVersion?: string }, + isDev = false +): DownloadDecision { + const { hash, diff, pdiff, full, paths = [] } = info; + if (!info.update || !hash) { + return { action: 'none', reason: 'noUpdate' }; + } + if (hash === identity.currentVersion) { + return { action: 'none', reason: 'alreadyCurrent' }; + } + if (identity.rolledBackVersion && hash === identity.rolledBackVersion) { + return { action: 'none', reason: 'rolledBack' }; + } + const attempts: DownloadAttempt[] = []; + // Incremental artifacts only exist against release builds; dev goes + // straight to full (or the no-op below when there is none). + if (!isDev) { + const diffUrls = joinUrls(paths, diff); + if (diffUrls?.length) { + attempts.push({ type: 'diff', urls: diffUrls }); + } + const pdiffUrls = joinUrls(paths, pdiff); + if (pdiffUrls?.length) { + attempts.push({ type: 'pdiff', urls: pdiffUrls }); + } + } + const fullUrls = joinUrls(paths, full); + if (fullUrls?.length) { + attempts.push({ type: 'full', urls: fullUrls }); + } + return { + action: 'download', + hash, + attempts, + devNoop: !!isDev && !fullUrls?.length, + }; +} diff --git a/src/utils.ts b/src/utils.ts index e6195686..caa13fb5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -76,18 +76,7 @@ const ping = isWeb } }; -export function joinUrls(paths: string[], fileName?: string) { - if (fileName) { - return paths.map((path) => { - const normalizedPath = path.replace(/\/+$/, ''); - // Keep explicit http(s) URLs for local/self-hosted update sources. - const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(normalizedPath) - ? normalizedPath - : `https://${normalizedPath}`; - return `${baseUrl}/${fileName}`; - }); - } -} +export { joinUrls } from './updateFlowCore'; export const testUrls = async (urls?: string[]): Promise => { if (!urls?.length) { From 40b5104ecfd215965e9de04839d6f222aa5f81f0 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 17:48:09 +0800 Subject: [PATCH 02/29] =?UTF-8?q?docs:=20native=20checkUpdate=20design=20?= =?UTF-8?q?=E2=80=94=20declarative=20endpoint=20plan,=20plain-text=20guard?= =?UTF-8?q?ian,=20no=20override=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three decisions land in the design: (1) buildCheckRequest returns a declarative endpoint plan and the native engine is a sequential fallback, not a port of the JS hedged race — the cold-start background path is latency-insensitive; (2) the guardian evaluates as plain-text JS source, never HBC, removing the bytecode-version coupling and narrowing the engine spike; (3) the remote override channel (§5.3-5.5) is cut — the decision layer is the most testable part of the pipeline, the JS-side checkUpdate remains a full fallback and the server can already route around decision bugs by shaping responses, so the premium exceeds the exposure. Phase status in §8 updated: protocol prerequisite lifted, pure-function extraction done (updateFlowCore.ts). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 86 +++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index d8d7bb63..03243d64 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -1,8 +1,9 @@ # 原生检测更新设计:让砖机也能被修好 -> 状态:设计草案,待评审 +> 状态:实施中——§8 第 2 步(纯函数抽取)已完成(`src/updateFlowCore.ts`,2026-08-07) > 取代:`REMOTE_RESET_DESIGN.md`(本地启动熔断方案,已放弃,理由见 §1.2) -> 前置:`BUNDLEHASH_DESIGN.md`(设计定稿、待实施)——协议下沉前必须先定稿协议 +> 前置:bundleHash 迁移 Phase 0/1/2 已上线且 buildTime 永久保留作 fallback 已定稿 +> (2026-08-03)——checkUpdate 的 wire protocol 已稳定,协议前置解除 > 关联:[[native-update-reset-design]] 的 Phase 3 --- @@ -82,6 +83,15 @@ A 直接淘汰。B 是 [[native-update-reset-design]] 里 Phase 3 的原计划 **B 与 C 的差别只有一个:检测逻辑本身出 bug 时,能不能不发新 binary 就修好。** +补充两点评审结论(2026-08-06):其一,B 并不消灭 TS 实现——app JS 侧的交互 +流程仍要用同一套决策逻辑,所以 B 实际是 **TS + C++ 双实现长期同步**(每次协 +议演进都要重编预编译 `.so`),而 C 是同一份 TS 源两处复用,这是 C 相对 B 的 +另一日常优势。其二,C 的独占收益比上表暗示的窄:只要 JS 侧 `checkUpdate` 保 +留为全功能回退(§6 是复用结果、不是拆除),原生检测逻辑出 bug 的最坏情形是 +正常设备仍被 JS 路径救起、只有砖机在窗口期救不了——即退回现状,而非"全网 +瘫"。真正集中风险的是原生**编排**代码(R2),而那部分 B / C 完全相同, +guardian 救不了它。 + --- ## 5. Guardian bundle @@ -93,31 +103,64 @@ A 直接淘汰。B 是 [[native-update-reset-design]] 里 Phase 3 的原计划 正确的边界是:**guardian 只做决策,IO 全部由原生执行。** ``` -原生 ──> guardian.buildCheckRequest(state) ──> { url, headers, body } -原生 <── (发 HTTP,各端现成客户端) +原生 ──> guardian.buildCheckRequest(state) ──> { endpoints, queryUrls, path, headers, body, timeoutMs } +原生 <── (按 endpoints 顺序逐个请求,单个超时即换下一个; + 首个失败后拉 queryUrls 合并远程候选、排除已失败的,继续顺序尝试) 原生 ──> guardian.decide(state, responseText) ──> { action, hash, url, type } | { action: 'none' } 原生 <── (下载 + patch_core 应用 + state_core switchVersion,全是现成的) 原生 ──> guardian.onOutcome(state, result) ──> { nextState } ``` +**endpoint 计划是声明式的**(初稿此处只画了"一问一答",漏掉了 JS 侧真实存在的 +多 endpoint 回退与远程 endpoint 发现):候选排序(随机首选分摊负载 + 配置序回 +退 + 失败排除)是纯策略,由 `orderEndpointCandidates` 给出,随机数由原生作为 +输入注入(guardian 不可自取随机)。**原生侧刻意不实现 JS 交互路径的 hedged +race**(`src/endpoint.ts` 的 250ms 错峰竞速):原生检测跑在冷启动后台、结果下 +次启动才生效,延迟不敏感(§7 R5 本来就要求延迟数秒),顺序回退 + 单请求超时 +就够了——换来的是三端各自的执行引擎退化为一个 for 循环,无定时器、无 abort +协调。两条路径共享同一份候选排序策略,只在并发形态上分叉。 + 这个边界带来的简化是决定性的: -- **不需要事件循环、不需要 Promise、不需要注入 HTTP** —— 只是同步求值一个小 HBC 再调几个函数 +- **不需要事件循环、不需要 Promise、不需要注入 HTTP** —— 只是同步求值一段小 JS 再调几个函数 - **隔离性天然成立** —— guardian 与 app bundle 是两个独立的 parse 单元、独立求值;app bundle 的语法错误跟它毫无关系 -- 灰度分桶(`isInRollout`)、diff→pdiff→full 选择、`expVersion` 解析、URL 拼接这些**已经是纯函数了**(`src/isInRollout.ts`、`src/resolveCheckResult.ts`),可以几乎原样搬过去 +- 灰度分桶、diff→pdiff→full 选择、`expVersion` 解析、URL 拼接、请求体构造、 + endpoint 排序**已全部抽为纯函数并在 JS 侧原地使用**(`src/updateFlowCore.ts`: + `buildCheckRequestBody` / `resolveCheckResult` / `decideDownload` / + `isInRollout` / `joinUrls` / `orderEndpointCandidates`,import 闭包为纯, + 可在裸引擎中求值),guardian 直接复用同一份源码 ### 5.2 运行时 -在后台线程创建一个 `hermes::makeHermesRuntime()`,求值 guardian 的 HBC,调用导出的函数,用完销毁。全同步,毫秒级。 +在后台线程创建一个 JS 运行时,求值 guardian 源码,调用导出的函数,用完销毁。全同步,毫秒级。 + +**已定稿(2026-08-06):guardian 以纯文本 JS 源码分发与求值,不用 HBC。** +Hermes 字节码格式不跨版本稳定——若分发 HBC,服务端覆盖包必须按宿主 Hermes +版本分桶,基线包也与构建期 hermesc 版本绑死,复杂度远超收益。源码求值走的 +是慢速路径,但 guardian 每次冷启动只求值一次、代码量千行级,毫秒级完全可接 +受。代价:宿主 libhermes 若编译时裁掉了源码编译器则不可用——这归入下面的链 +接可行性验证。 放在 `cpp/` 里与 `patch_core` / `state_core` 同级,三端共享同一份 C++;各端只提供 HTTP 客户端(Android OkHttp、iOS NSURLSession、Harmony `@ohos.net.http` —— 都已在用)和文件路径。 -**主要风险**:直接链接 libhermes 的 C++ API 在 RN 各版本间会变,可能出现符号/ABI 兼容问题。Android 的 `librnupdate.so` 是预编译产物(4 个 ABI),链上 Hermes 后与宿主 RN 的 Hermes 版本耦合。**这是 C 相对 B 的主要代价,需要先做一个链接可行性验证再决定**。 +**主要风险**:直接链接 libhermes 的 C++ API 在 RN 各版本间会变,可能出现符号/ABI 兼容问题。Android 的 `librnupdate.so` 是预编译产物(4 个 ABI),链上 Hermes 后与宿主 RN 的 Hermes 版本耦合。**这是 C 相对 B 的主要代价,需要先做一个链接可行性验证再决定**(验证已推迟到纯函数抽取完成之后;纯文本 JS 定稿后,验证范围收窄为"能否拿到一个可求值源码的引擎",不再含字节码版本矩阵)。 -备选:不用 Hermes,用一个极小的嵌入式 JS 引擎(QuickJS 约 200KB)。彻底解耦宿主 RN,代价是包体增加与另一套字节码工具链。 +备选:不用 Hermes,用一个极小的嵌入式 JS 引擎(QuickJS 约 200KB)。彻底解耦宿主 RN,代价是包体增加;纯文本分发下不需要其字节码工具链。若验证结论是"只有 QuickJS 才干净",应改选方案 B——C 的独占收益(窗口期远程修决策纯函数的 bug)撑不起这个包体与供应链成本。 ### 5.3 分发与覆盖 +> **裁决(2026-08-07):远程覆盖通道不做,guardian 只带随 binary 打包的基线。** +> 覆盖通道买的保险是"决策纯函数出 bug 时不发 binary 就能修",但决策层是全 +> 链路最可测的部分(纯函数、单测、与 JS 侧同一份源码),且已有两条兜底: +> JS 侧 checkUpdate 全功能回退(决策 bug 最坏退回现状,不是新增灾难)、服 +> 务端塑造响应/重绑版本本身就是一条远程修复通道(决策层消费的是服务端数 +> 据)。代价却是全系统最敏感的安全面——启动最早期执行、有权决定装什么版本 +> 的服务端下发代码——加上 §5.4/§5.5 的全部工程量,而覆盖机制自身是原生代 +> 码,它出 bug 同样无法远程修。保费高于风险敞口。 +> 未来若决策层 bug 真在现场咬人,优先评估**搭现有热更通道便车**(ppk 附带 +> guardian.js,复用既有 hash 校验与下发权限),不自建通道。§5.4/§5.5 保留 +> 作为那时的设计输入;"救砖"能力来自 §8 第 4 步的原生编排,不受本裁决影响。 + | | 来源 | 作用 | |---|---|---| | 基线 | 随 binary 打包(asset / rawfile) | 永远存在,已随发版验证过 | @@ -174,13 +217,24 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示 ## 8. 分期 -1. **bundleHash 迁移**(`BUNDLEHASH_DESIGN.md`,已定稿)—— 协议下沉的前置,否则返工 -2. **纯函数抽取** —— 把 `buildCheckRequest` / `decide` 从 `client.ts` 剥成无 IO 的纯函数,先在 JS 侧原地使用并补测试。这一步 B / C 都需要,且不依赖运行时选型,**可以立刻开始** -3. **运行时选型** —— Hermes 链接可行性验证;失败则退回方案 B(C++ 纯函数) -4. **原生编排** —— 三端 HTTP + 调用纯函数 + 复用现有下载/patch/state -5. **guardian 分发通道**(仅方案 C)—— 打包、下发、§5.4 回滚保护 - -第 2 步是关键:**它是 B 和 C 的公共前置**,做完之后再决定选型也不迟,而且它本身就能让现有 JS 实现更可测。 +1. ~~**bundleHash 迁移**~~ **已解除**(2026-08-03)—— Phase 0/1/2 已上线、 + 判定开关双端开启、buildTime 永久保留作 fallback 定稿,checkUpdate 的 + wire protocol 已稳定;剩余的 Phase 3(SyncBinaryVersion 迁移)是客户端 + 本地状态变更,不动协议,与本方案只需在落地顺序上错开(都动原生启动路 + 径与状态 schema),不再构成前置 +2. ~~**纯函数抽取**~~ **已完成**(2026-08-07)—— `src/updateFlowCore.ts`: + `buildCheckRequestBody` / `resolveCheckResult` / `decideDownload` / + `isInRollout` / `joinUrls` / `orderEndpointCandidates`,无 IO、无 + react-native 依赖、无模块级状态(身份/随机数均参数注入),import 闭包 + 为纯;client.ts / provider.tsx / endpoint.ts 已原地改用,单测覆盖 +3. **运行时选型** —— Hermes 链接可行性验证(已推迟;纯文本 JS 定稿后范围 + 收窄,见 §5.2);失败则退回方案 B(C++ 纯函数) +4. **原生编排** —— 三端 HTTP + 调用纯函数 + 复用现有下载/patch/state; + endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race +5. ~~**guardian 分发通道**~~ **已裁决不做**(2026-08-07,见 §5.3)—— + guardian 只带基线、无远程覆盖;砖机救援能力在第 4 步,不受影响 + +第 2 步曾是关键路径(B 和 C 的公共前置),现已完成——下一个决策点是第 3 步的求值引擎验证,其结论裁决"决策层用 JS 源码求值还是 C++ 重写"(第 4 步两个结果都要做,路线不再分叉)。 ## 9. 已写代码的处置 From b19413b7f66f2b992a30967cd603152f7eb1b08f Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 17:51:17 +0800 Subject: [PATCH 03/29] =?UTF-8?q?docs:=20guardian=20source=20portability?= =?UTF-8?q?=20verified=20=E2=80=94=20engine=20choice=20goes=20per-platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bundled updateFlowCore (6.4KB plain-text JS, zero deps) produces byte-identical correct output under bare JSC, Hermes (RN 0.73 VM, source eval, no HBC) and V8, and a 40-line ObjC host evaluates it via JSContext and calls decideDownload end to end — the iOS orchestrator shape, linking only system frameworks. Since the source is the shared artifact, the engine is now chosen per platform: iOS system JavaScriptCore (zero risk, proven), Harmony system JSVM-API, Android remains the only open spike (javascriptengine / Hermes linking / QuickJS; worst case Android alone falls back to a C++ decision layer as a hybrid). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 03243d64..5cefaa41 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -141,11 +141,28 @@ Hermes 字节码格式不跨版本稳定——若分发 HBC,服务端覆盖包 受。代价:宿主 libhermes 若编译时裁掉了源码编译器则不可用——这归入下面的链 接可行性验证。 -放在 `cpp/` 里与 `patch_core` / `state_core` 同级,三端共享同一份 C++;各端只提供 HTTP 客户端(Android OkHttp、iOS NSURLSession、Harmony `@ohos.net.http` —— 都已在用)和文件路径。 +**源码可移植性已验证(2026-08-07)**:`src/updateFlowCore.ts` 经 +`bun build --format=cjs` 打成 6.4KB 单文件纯文本 JS(零依赖、零 require), +在三个裸引擎下对同一驱动脚本的输出**逐字节一致**且全部正确——macOS 系统 +`jsc`(与 iOS JavaScriptCore 同源)、Hermes VM(RN 0.73 配套版本,源码直接 +求值无需 HBC)、Node V8。函数调用边界(JSON 进出)即 §5.1 的三步接口,工作 +正常。进一步用 ObjC 写了最小原生宿主实测通过:`JSContext` 求值源码 → +`callWithArguments` 调 `decideDownload` → 拿回决策 JSON——40 行代码、仅链 +系统 Foundation + JavaScriptCore 两个框架,就是 iOS 编排器的最终形态。语法下限:产物含 `?.`/`??`/对象展开 → Hermes ≥0.7(RN 0.64+)、iOS +JSC ≥13.4;如需更老目标,guardian 构建加 es2015 降级即可。 + +**纯文本定稿的推论——三端不必用同一个引擎**:源码是共享物,引擎只是求值 +器,选型可以按端就地取材: + +| 端 | 引擎 | 风险 | +|---|---|---| +| iOS | 系统 JavaScriptCore 框架 | **零**——系统框架,零链接、零包体、零版本耦合,上面已实测同源引擎 | +| Harmony | 系统 JSVM-API(V8,API 11+,RNOH 本就要求 5.0+) | 低——系统能力,待落地时确认 API 细节 | +| Android | 开放问题:androidx.javascriptengine(系统 WebView 引擎、沙箱进程、异步)/ 链接 Hermes(ABI 耦合宿主 RN)/ QuickJS(~200KB×4 ABI) | **剩余的全部 spike 范围** | -**主要风险**:直接链接 libhermes 的 C++ API 在 RN 各版本间会变,可能出现符号/ABI 兼容问题。Android 的 `librnupdate.so` 是预编译产物(4 个 ABI),链上 Hermes 后与宿主 RN 的 Hermes 版本耦合。**这是 C 相对 B 的主要代价,需要先做一个链接可行性验证再决定**(验证已推迟到纯函数抽取完成之后;纯文本 JS 定稿后,验证范围收窄为"能否拿到一个可求值源码的引擎",不再含字节码版本矩阵)。 +原生编排层(HTTP、下载、状态)仍放 `cpp/` 与 `patch_core` / `state_core` 同级三端共享;求值器作为注入接口由各端实现,与 HTTP 客户端同一地位。 -备选:不用 Hermes,用一个极小的嵌入式 JS 引擎(QuickJS 约 200KB)。彻底解耦宿主 RN,代价是包体增加;纯文本分发下不需要其字节码工具链。若验证结论是"只有 QuickJS 才干净",应改选方案 B——C 的独占收益(窗口期远程修决策纯函数的 bug)撑不起这个包体与供应链成本。 +**剩余风险收窄为 Android 单点**:直接链接 libhermes 的 C++ API 在 RN 各版本间会变,且 `librnupdate.so` 是预编译产物(4 个 ABI)。若三个 Android 候选都不干净,**仅 Android 一端退回方案 B(决策逻辑 C++ 重写)也是可接受的混合形态**——三端源码一致性只在 iOS/Harmony 保持,Android 以 updateFlowCore 为参照实现并靠共享测试向量对齐。 ### 5.3 分发与覆盖 @@ -227,8 +244,10 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示 `isInRollout` / `joinUrls` / `orderEndpointCandidates`,无 IO、无 react-native 依赖、无模块级状态(身份/随机数均参数注入),import 闭包 为纯;client.ts / provider.tsx / endpoint.ts 已原地改用,单测覆盖 -3. **运行时选型** —— Hermes 链接可行性验证(已推迟;纯文本 JS 定稿后范围 - 收窄,见 §5.2);失败则退回方案 B(C++ 纯函数) +3. **运行时选型** —— 源码可移植性已验证(2026-08-07,三裸引擎逐字节一 + 致,见 §5.2);iOS 定为系统 JavaScriptCore、Harmony 定为系统 JSVM-API, + **剩余 spike 仅 Android**(javascriptengine / Hermes 链接 / QuickJS 三 + 选一,全不干净则 Android 单端退回 C++ 实现的混合形态) 4. **原生编排** —— 三端 HTTP + 调用纯函数 + 复用现有下载/patch/state; endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race 5. ~~**guardian 分发通道**~~ **已裁决不做**(2026-08-07,见 §5.3)—— From 7eb9970f079714bd02ce8e33806880f4243151bb Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 17:52:57 +0800 Subject: [PATCH 04/29] =?UTF-8?q?docs:=20rule=20out=20linking=20the=20host?= =?UTF-8?q?=20Hermes=20on=20Android=20=E2=80=94=20C=20ABI=20not=20exported?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symbol recon on the prebuilt hermes-android 250829098.0.10 (RN 0.85): the stable C ABI header (hermes_abi.h) ships in the prefab but its entry point get_hermes_abi_vtable is not exported from libhermesvm.so; only the C++ makeHermesRuntime is, which couples the caller to the host's exact RuntimeConfig layout and requires compiling a matching jsi.cpp (zero JSI symbols exported). For a prebuilt librnupdate.so shipped across RN versions that is an unmanageable matrix. Android candidates narrow to androidx.javascriptengine (needs on-device verification) and QuickJS; the route revives if upstream ever exports the stable C ABI. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 5cefaa41..59de3e7b 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -158,7 +158,19 @@ JSC ≥13.4;如需更老目标,guardian 构建加 es2015 降级即可。 |---|---|---| | iOS | 系统 JavaScriptCore 框架 | **零**——系统框架,零链接、零包体、零版本耦合,上面已实测同源引擎 | | Harmony | 系统 JSVM-API(V8,API 11+,RNOH 本就要求 5.0+) | 低——系统能力,待落地时确认 API 细节 | -| Android | 开放问题:androidx.javascriptengine(系统 WebView 引擎、沙箱进程、异步)/ 链接 Hermes(ABI 耦合宿主 RN)/ QuickJS(~200KB×4 ABI) | **剩余的全部 spike 范围** | +| Android | 开放问题:androidx.javascriptengine(系统 WebView 引擎、沙箱进程、异步)/ QuickJS(~200KB×4 ABI)/ ~~链接宿主 Hermes~~ | **剩余的全部 spike 范围**(Hermes 路线已被侦察排除,见下) | + +**Android 链接宿主 Hermes 已排除(2026-08-07 对 hermes-android +250829098.0.10 / RN 0.85 预编译产物的符号侦察)**:prefab 里虽然带了 +`hermes_abi/hermes_abi.h`(稳定 C ABI 头),但其入口 `get_hermes_abi_vtable` +**并未从 `libhermesvm.so` 导出**——只导出了 C++ 的 +`facebook::hermes::makeHermesRuntime(RuntimeConfig const&)`,而走 C++ 路线 +要求调用方与宿主版本的 `RuntimeConfig` 布局、libc++ ABI 精确匹配,且 JSI 符 +号导出为零(消费方须自行编译与宿主一致版本的 jsi.cpp)。对跨 RN 版本分发的 +预编译 `librnupdate.so`,这是不可控的版本矩阵;老 RN 的 `libhermes.so` 连库 +名和导出面都不同。若未来官方开始导出稳定 C ABI,此路线可复活。当前 Android +实际候选就两个:javascriptengine(零包体、依赖 WebView provider、沙箱进程异 +步——对冷启动后台任务可接受,需真机验证)与 QuickJS(确定可行、包体代价)。 原生编排层(HTTP、下载、状态)仍放 `cpp/` 与 `patch_core` / `state_core` 同级三端共享;求值器作为注入接口由各端实现,与 HTTP 客户端同一地位。 From d843213127412ce79a887378ad3bfcb29f1637e3 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 22:43:34 +0800 Subject: [PATCH 05/29] feat: port the update-flow decision layer to C++ with golden-vector parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpp/update_flow_core is a 1:1 port of src/updateFlowCore.ts (the reference implementation): Murmur3_32, IsInRollout, JoinUrls, OrderEndpointCandidates, BuildCheckRequestBody, ResolveCheckResult and DecideDownload, built on a minimal flow_json DOM that mirrors the JS semantics parity depends on — insertion-ordered objects (overwrite keeps position), undefined distinct from null and skipped by stringify, JS truthiness and strict equality. Parity is a mechanically enforced contract, not a convention: scripts/generate-flow-vectors.ts runs the TS reference over a dense input matrix and writes cpp/update_flow_core/tests/flow_vectors.json (69 cases); src/__tests__/flowVectors.test.ts fails when the TS side and the committed vectors disagree, and scripts/test-update-flow-core.sh (wired into the CI cpp-test job under ASan+UBSan) replays the same vectors against the C++ port. Semantic changes land TS-first, regenerate the vectors, then port. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yml | 6 + cpp/update_flow_core/flow_json.cpp | 471 ++++++ cpp/update_flow_core/flow_json.h | 102 ++ cpp/update_flow_core/tests/flow_vectors.json | 1307 +++++++++++++++++ .../tests/update_flow_core_test.cpp | 101 ++ cpp/update_flow_core/update_flow_core.cpp | 264 ++++ cpp/update_flow_core/update_flow_core.h | 51 + package.json | 2 + scripts/generate-flow-vectors.ts | 265 ++++ scripts/test-update-flow-core.sh | 28 + src/__tests__/flowVectors.test.ts | 18 + 11 files changed, 2615 insertions(+) create mode 100644 cpp/update_flow_core/flow_json.cpp create mode 100644 cpp/update_flow_core/flow_json.h create mode 100644 cpp/update_flow_core/tests/flow_vectors.json create mode 100644 cpp/update_flow_core/tests/update_flow_core_test.cpp create mode 100644 cpp/update_flow_core/update_flow_core.cpp create mode 100644 cpp/update_flow_core/update_flow_core.h create mode 100644 scripts/generate-flow-vectors.ts create mode 100755 scripts/test-update-flow-core.sh create mode 100644 src/__tests__/flowVectors.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 390c2628..3428e602 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,3 +57,9 @@ jobs: -lpthread \ -o unit_test ./unit_test + + # Golden-vector parity between src/updateFlowCore.ts (reference) and + # the cpp/update_flow_core port. The vectors themselves are kept in + # sync with the TS side by src/__tests__/flowVectors.test.ts (js-test). + - name: Replay flow golden vectors against the C++ port + run: SANITIZE=1 ./scripts/test-update-flow-core.sh diff --git a/cpp/update_flow_core/flow_json.cpp b/cpp/update_flow_core/flow_json.cpp new file mode 100644 index 00000000..89c1ad03 --- /dev/null +++ b/cpp/update_flow_core/flow_json.cpp @@ -0,0 +1,471 @@ +#include "flow_json.h" + +#include +#include +#include + +namespace flowjson { + +namespace { +const Value kUndefined; +} // namespace + +bool Value::Truthy() const { + switch (kind_) { + case Kind::Undefined: + case Kind::Null: + return false; + case Kind::Bool: + return bool_; + case Kind::Number: + return number_ != 0 && !std::isnan(number_); + case Kind::String: + return !string_.empty(); + case Kind::Array: + case Kind::Object: + return true; + } + return false; +} + +const Value& Value::At(size_t i) const { + if (kind_ != Kind::Array || i >= elements_.size()) { + return kUndefined; + } + return elements_[i]; +} + +const Value& Value::Get(const std::string& key) const { + if (kind_ == Kind::Object) { + for (const auto& member : members_) { + if (member.first == key) { + return member.second; + } + } + } + return kUndefined; +} + +void Value::Set(const std::string& key, Value v) { + for (auto& member : members_) { + if (member.first == key) { + member.second = std::move(v); + return; + } + } + members_.emplace_back(key, std::move(v)); +} + +void Value::Remove(const std::string& key) { + for (auto it = members_.begin(); it != members_.end(); ++it) { + if (it->first == key) { + members_.erase(it); + return; + } + } +} + +bool Value::StrictEquals(const Value& a, const Value& b) { + if (a.kind_ != b.kind_) { + return false; + } + switch (a.kind_) { + case Kind::Undefined: + case Kind::Null: + return true; + case Kind::Bool: + return a.bool_ == b.bool_; + case Kind::Number: + return a.number_ == b.number_; + case Kind::String: + return a.string_ == b.string_; + case Kind::Array: + case Kind::Object: + return false; + } + return false; +} + +namespace { + +void AppendEscaped(const std::string& s, std::string* out) { + out->push_back('"'); + for (unsigned char c : s) { + switch (c) { + case '"': + out->append("\\\""); + break; + case '\\': + out->append("\\\\"); + break; + case '\b': + out->append("\\b"); + break; + case '\f': + out->append("\\f"); + break; + case '\n': + out->append("\\n"); + break; + case '\r': + out->append("\\r"); + break; + case '\t': + out->append("\\t"); + break; + default: + if (c < 0x20) { + char buf[8]; + std::snprintf(buf, sizeof(buf), "\\u%04x", c); + out->append(buf); + } else { + out->push_back(static_cast(c)); + } + } + } + out->push_back('"'); +} + +void AppendNumber(double n, std::string* out) { + // The decision layer only ever emits integral numbers (hashes, counters, + // rollout percentages); print them the way JSON.stringify does. The %.17g + // fallback exists so an unexpected fractional value surfaces as a vector + // mismatch instead of silent truncation. + if (std::isfinite(n) && n == std::floor(n) && std::fabs(n) <= 9007199254740992.0) { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%lld", static_cast(n)); + out->append(buf); + } else { + char buf[40]; + std::snprintf(buf, sizeof(buf), "%.17g", n); + out->append(buf); + } +} + +void AppendValue(const Value& v, std::string* out) { + switch (v.kind()) { + case Value::Kind::Undefined: // only reachable inside arrays + case Value::Kind::Null: + out->append("null"); + break; + case Value::Kind::Bool: + out->append(v.AsBool() ? "true" : "false"); + break; + case Value::Kind::Number: + AppendNumber(v.AsNumber(), out); + break; + case Value::Kind::String: + AppendEscaped(v.AsString(), out); + break; + case Value::Kind::Array: { + out->push_back('['); + bool first = true; + for (const auto& element : v.elements()) { + if (!first) { + out->push_back(','); + } + first = false; + AppendValue(element, out); + } + out->push_back(']'); + break; + } + case Value::Kind::Object: { + out->push_back('{'); + bool first = true; + for (const auto& member : v.members()) { + if (member.second.IsUndefined()) { + continue; // JSON.stringify drops undefined-valued members + } + if (!first) { + out->push_back(','); + } + first = false; + AppendEscaped(member.first, out); + out->push_back(':'); + AppendValue(member.second, out); + } + out->push_back('}'); + break; + } + } +} + +} // namespace + +std::string Stringify(const Value& v) { + if (v.IsUndefined()) { + return "undefined"; + } + std::string out; + AppendValue(v, &out); + return out; +} + +namespace { + +class Parser { + public: + Parser(const std::string& text) : text_(text) {} + + Value Run(bool* ok) { + Value v = ParseValue(); + SkipWs(); + *ok = ok_ && pos_ == text_.size(); + return *ok ? v : Value::Undefined(); + } + + private: + void SkipWs() { + while (pos_ < text_.size()) { + char c = text_[pos_]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + pos_++; + } else { + break; + } + } + } + + bool Consume(char expected) { + if (pos_ < text_.size() && text_[pos_] == expected) { + pos_++; + return true; + } + ok_ = false; + return false; + } + + bool ConsumeLiteral(const char* literal) { + size_t len = 0; + while (literal[len]) { + len++; + } + if (text_.compare(pos_, len, literal) == 0) { + pos_ += len; + return true; + } + ok_ = false; + return false; + } + + Value ParseValue() { + SkipWs(); + if (pos_ >= text_.size()) { + ok_ = false; + return Value::Undefined(); + } + char c = text_[pos_]; + switch (c) { + case '{': + return ParseObject(); + case '[': + return ParseArray(); + case '"': + return Value::String(ParseString()); + case 't': + ConsumeLiteral("true"); + return Value::Bool(true); + case 'f': + ConsumeLiteral("false"); + return Value::Bool(false); + case 'n': + ConsumeLiteral("null"); + return Value::Null(); + default: + return ParseNumber(); + } + } + + Value ParseObject() { + Value obj = Value::Object(); + Consume('{'); + SkipWs(); + if (pos_ < text_.size() && text_[pos_] == '}') { + pos_++; + return obj; + } + while (ok_) { + SkipWs(); + std::string key = ParseString(); + SkipWs(); + Consume(':'); + obj.Set(key, ParseValue()); + SkipWs(); + if (pos_ < text_.size() && text_[pos_] == ',') { + pos_++; + continue; + } + Consume('}'); + break; + } + return obj; + } + + Value ParseArray() { + Value arr = Value::Array(); + Consume('['); + SkipWs(); + if (pos_ < text_.size() && text_[pos_] == ']') { + pos_++; + return arr; + } + while (ok_) { + arr.Push(ParseValue()); + SkipWs(); + if (pos_ < text_.size() && text_[pos_] == ',') { + pos_++; + continue; + } + Consume(']'); + break; + } + return arr; + } + + std::string ParseString() { + std::string out; + if (!Consume('"')) { + return out; + } + while (pos_ < text_.size()) { + char c = text_[pos_++]; + if (c == '"') { + return out; + } + if (c != '\\') { + out.push_back(c); + continue; + } + if (pos_ >= text_.size()) { + break; + } + char esc = text_[pos_++]; + switch (esc) { + case '"': + case '\\': + case '/': + out.push_back(esc); + break; + case 'b': + out.push_back('\b'); + break; + case 'f': + out.push_back('\f'); + break; + case 'n': + out.push_back('\n'); + break; + case 'r': + out.push_back('\r'); + break; + case 't': + out.push_back('\t'); + break; + case 'u': { + unsigned code = ParseHex4(); + // BMP code point to UTF-8 (surrogate pairs are combined). + if (code >= 0xd800 && code <= 0xdbff && + text_.compare(pos_, 2, "\\u") == 0) { + pos_ += 2; + unsigned low = ParseHex4(); + if (low >= 0xdc00 && low <= 0xdfff) { + code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00); + } else { + ok_ = false; + } + } + AppendUtf8(code, &out); + break; + } + default: + ok_ = false; + return out; + } + } + ok_ = false; + return out; + } + + unsigned ParseHex4() { + unsigned code = 0; + for (int i = 0; i < 4; i++) { + if (pos_ >= text_.size()) { + ok_ = false; + return 0; + } + char c = text_[pos_++]; + code <<= 4; + if (c >= '0' && c <= '9') { + code |= static_cast(c - '0'); + } else if (c >= 'a' && c <= 'f') { + code |= static_cast(c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + code |= static_cast(c - 'A' + 10); + } else { + ok_ = false; + return 0; + } + } + return code; + } + + static void AppendUtf8(unsigned code, std::string* out) { + if (code < 0x80) { + out->push_back(static_cast(code)); + } else if (code < 0x800) { + out->push_back(static_cast(0xc0 | (code >> 6))); + out->push_back(static_cast(0x80 | (code & 0x3f))); + } else if (code < 0x10000) { + out->push_back(static_cast(0xe0 | (code >> 12))); + out->push_back(static_cast(0x80 | ((code >> 6) & 0x3f))); + out->push_back(static_cast(0x80 | (code & 0x3f))); + } else { + out->push_back(static_cast(0xf0 | (code >> 18))); + out->push_back(static_cast(0x80 | ((code >> 12) & 0x3f))); + out->push_back(static_cast(0x80 | ((code >> 6) & 0x3f))); + out->push_back(static_cast(0x80 | (code & 0x3f))); + } + } + + Value ParseNumber() { + size_t start = pos_; + if (pos_ < text_.size() && text_[pos_] == '-') { + pos_++; + } + while (pos_ < text_.size()) { + char c = text_[pos_]; + if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || + c == '+' || c == '-') { + pos_++; + } else { + break; + } + } + if (pos_ == start) { + ok_ = false; + return Value::Undefined(); + } + char* end = nullptr; + std::string token = text_.substr(start, pos_ - start); + double n = std::strtod(token.c_str(), &end); + if (end == nullptr || *end != '\0') { + ok_ = false; + return Value::Undefined(); + } + return Value::Number(n); + } + + const std::string& text_; + size_t pos_ = 0; + bool ok_ = true; +}; + +} // namespace + +Value Parse(const std::string& text, bool* ok) { + Parser parser(text); + return parser.Run(ok); +} + +} // namespace flowjson diff --git a/cpp/update_flow_core/flow_json.h b/cpp/update_flow_core/flow_json.h new file mode 100644 index 00000000..84947ea2 --- /dev/null +++ b/cpp/update_flow_core/flow_json.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +// Minimal JSON document model for the update-flow decision layer. +// +// Deliberately mirrors JavaScript object semantics where the ported decision +// logic depends on them, because the golden vectors are generated by the TS +// reference implementation and compared as serialized strings: +// - object members keep insertion order, and overwriting an existing key +// keeps its original position (JS spread/assignment semantics); +// - Kind::Undefined is distinct from Kind::Null: undefined-valued members +// are skipped by Stringify (like JSON.stringify), null is emitted; +// - Truthy() implements JS truthiness (undefined/null/false/0/NaN/'' are +// falsy; empty arrays and objects are truthy); +// - StrictEquals() implements === for primitives only (never for +// arrays/objects — reference equality cannot hold across a parse). +namespace flowjson { + +class Value; +using Members = std::vector>; +using Elements = std::vector; + +class Value { + public: + enum class Kind { Undefined, Null, Bool, Number, String, Array, Object }; + + Value() = default; + static Value Undefined() { return Value(); } + static Value Null() { return Value(Kind::Null); } + static Value Bool(bool b) { + Value v(Kind::Bool); + v.bool_ = b; + return v; + } + static Value Number(double n) { + Value v(Kind::Number); + v.number_ = n; + return v; + } + static Value String(std::string s) { + Value v(Kind::String); + v.string_ = std::move(s); + return v; + } + static Value Array() { return Value(Kind::Array); } + static Value Object() { return Value(Kind::Object); } + + Kind kind() const { return kind_; } + bool IsUndefined() const { return kind_ == Kind::Undefined; } + bool IsArray() const { return kind_ == Kind::Array; } + bool IsObject() const { return kind_ == Kind::Object; } + bool IsNumber() const { return kind_ == Kind::Number; } + bool IsString() const { return kind_ == Kind::String; } + + bool AsBool() const { return bool_; } + double AsNumber() const { return number_; } + const std::string& AsString() const { return string_; } + + bool Truthy() const; + + // Array access. + const Elements& elements() const { return elements_; } + void Push(Value v) { elements_.push_back(std::move(v)); } + size_t Size() const { return elements_.size(); } + const Value& At(size_t i) const; + + // Object access. Get returns Undefined for a missing key; Set overwrites + // in place (keeping the key's position) or appends. + const Members& members() const { return members_; } + const Value& Get(const std::string& key) const; + void Set(const std::string& key, Value v); + void Remove(const std::string& key); + + static bool StrictEquals(const Value& a, const Value& b); + + private: + explicit Value(Kind kind) : kind_(kind) {} + + Kind kind_ = Kind::Undefined; + bool bool_ = false; + double number_ = 0; + std::string string_; + Elements elements_; + Members members_; +}; + +// JSON.stringify-compatible for the value shapes the decision layer produces +// (undefined members skipped, undefined array elements become null, integral +// numbers within the double-safe range print without a decimal point). A +// top-level Undefined prints as "undefined" so two undefined results compare +// equal. +std::string Stringify(const Value& v); + +// Strict JSON parser (the vectors file and checkUpdate responses). Returns +// Undefined and sets *ok to false on malformed input. +Value Parse(const std::string& text, bool* ok); + +} // namespace flowjson diff --git a/cpp/update_flow_core/tests/flow_vectors.json b/cpp/update_flow_core/tests/flow_vectors.json new file mode 100644 index 00000000..8b4822cf --- /dev/null +++ b/cpp/update_flow_core/tests/flow_vectors.json @@ -0,0 +1,1307 @@ +{ + "generated_by": "scripts/generate-flow-vectors.ts — do not edit by hand", + "cases": [ + { + "fn": "murmurhash3_32_gc", + "args": [ + "" + ], + "expected": 0 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "hello" + ], + "expected": 613153351 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "test" + ], + "expected": 3127628307 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "Hello, world!" + ], + "expected": 3224780355 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "The quick brown fox jumps over the lazy dog" + ], + "expected": 776992547 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "test1" + ], + "expected": 374203662 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "test2" + ], + "expected": 3155333867 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "test3" + ], + "expected": 4144626353 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "123e4567-e89b-12d3-a456-426614174000" + ], + "expected": 439536286 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "123e4567-e89b-12d3-a456-426614174001" + ], + "expected": 3316680088 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "a" + ], + "expected": 1009084850 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "ab" + ], + "expected": 2613040991 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "abc" + ], + "expected": 3017643002 + }, + { + "fn": "murmurhash3_32_gc", + "args": [ + "abcd" + ], + "expected": 1139631978 + }, + { + "fn": "isInRollout", + "args": [ + 63, + "test1" + ], + "expected": true + }, + { + "fn": "isInRollout", + "args": [ + 62, + "test1" + ], + "expected": false + }, + { + "fn": "isInRollout", + "args": [ + 61, + "test1" + ], + "expected": false + }, + { + "fn": "isInRollout", + "args": [ + 0, + "test1" + ], + "expected": false + }, + { + "fn": "isInRollout", + "args": [ + 100, + "test1" + ], + "expected": true + }, + { + "fn": "isInRollout", + "args": [ + -1, + "test3" + ], + "expected": false + }, + { + "fn": "isInRollout", + "args": [ + 54, + "test3" + ], + "expected": true + }, + { + "fn": "isInRollout", + "args": [ + 53, + "test3" + ], + "expected": false + }, + { + "fn": "joinUrls", + "args": [ + [ + "example.com" + ] + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "example.com" + ], + "" + ] + }, + { + "fn": "joinUrls", + "args": [ + [], + "file.txt" + ], + "expected": [] + }, + { + "fn": "joinUrls", + "args": [ + [ + "example.com", + "test.org" + ], + "file.txt" + ], + "expected": [ + "https://example.com/file.txt", + "https://test.org/file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "example.com///", + "http://example.com///" + ], + "file.txt" + ], + "expected": [ + "https://example.com/file.txt", + "http://example.com/file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "ftp://example.com", + "myapp://some/path" + ], + "file.txt" + ], + "expected": [ + "ftp://example.com/file.txt", + "myapp://some/path/file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "192.168.1.1:8080", + "10.0.0.1:3000/api" + ], + "file.txt" + ], + "expected": [ + "https://192.168.1.1:8080/file.txt", + "https://10.0.0.1:3000/api/file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "" + ], + "file.txt" + ], + "expected": [ + "https:///file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "HTTPS://Upper.example.com" + ], + "file.txt" + ], + "expected": [ + "HTTPS://Upper.example.com/file.txt" + ] + }, + { + "fn": "joinUrls", + "args": [ + [ + "a:b://weird" + ], + "file.txt" + ], + "expected": [ + "https://a:b://weird/file.txt" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + "b", + "c" + ], + 0 + ], + "expected": [ + "a", + "b", + "c" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + "b", + "c" + ], + 0.34 + ], + "expected": [ + "b", + "a", + "c" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + "b", + "c" + ], + 0.5 + ], + "expected": [ + "b", + "a", + "c" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + "b", + "c" + ], + 0.99 + ], + "expected": [ + "c", + "a", + "b" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + "b", + "c" + ], + 1 + ], + "expected": [ + "c", + "a", + "b" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a", + null, + "a", + "", + "b" + ], + 0.6 + ], + "expected": [ + "b", + "a" + ] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [], + 0.5 + ], + "expected": [] + }, + { + "fn": "orderEndpointCandidates", + "args": [ + [ + "a" + ], + 0.5 + ], + "expected": [ + "a" + ] + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "currentVersion": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + ], + "expected": { + "packageVersion": "2.3.4", + "hash": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "currentVersion": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "supportedDiffVersion": 2, + "bundleHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ], + "expected": { + "packageVersion": "2.3.4", + "hash": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "diffV": 2, + "bundleHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "currentVersion": "", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "supportedDiffVersion": 0, + "bundleHash": "" + } + ], + "expected": { + "packageVersion": "2.3.4", + "hash": "", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "currentVersion": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "extra": { + "toHash": "debug-hash", + "hash": "override-hash" + } + } + ], + "expected": { + "packageVersion": "2.3.4", + "hash": "override-hash", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "toHash": "debug-hash" + } + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "currentVersion": "abcdef1234", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + }, + "isDev": true, + "extra": { + "buildTime": "injected" + } + } + ], + "expected": { + "packageVersion": "2.3.4", + "hash": "abcdef1234", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + }, + { + "fn": "buildCheckRequestBody", + "args": [ + { + "packageVersion": "2.3.4", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + ], + "expected": { + "packageVersion": "2.3.4", + "buildTime": "1719999999", + "cInfo": { + "rnu": "10.50.0", + "rn": "0.85.2", + "os": "ios 17.5", + "uuid": "u-1" + } + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ], + "expVersion": { + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + } + } + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": true, + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + }, + "paths": [ + "cdn.example.com" + ] + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ], + "expVersion": { + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 62 + } + } + } + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ] + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ], + "expVersion": { + "name": "gray", + "hash": "current-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 100 + } + } + } + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "upToDate": true + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ] + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ] + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "current-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ] + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "upToDate": true + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "upToDate": true + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "upToDate": true + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": false, + "hash": "x" + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": false, + "hash": "x" + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ], + "expVersion": { + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "other": 100 + } + } + } + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": true, + "hash": "root-hash", + "name": "root", + "description": "rd", + "metaInfo": "rm", + "diff": "a.hdiff", + "pdiff": "b.phdiff", + "full": "c.ppk", + "paths": [ + "cdn.example.com" + ] + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "expVersion": { + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + } + } + }, + { + "packageVersion": "2.3.4", + "currentVersion": "current-hash", + "uuid": "test1" + } + ], + "expected": { + "update": true, + "name": "gray", + "hash": "gray-hash", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + } + } + }, + { + "fn": "resolveCheckResult", + "args": [ + { + "update": true, + "hash": "root-hash", + "expVersion": { + "name": "gray", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + } + } + }, + { + "packageVersion": "2.3.4", + "uuid": "test1" + } + ], + "expected": { + "upToDate": true + } + }, + { + "fn": "decideDownload", + "args": [ + { + "upToDate": true + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "none", + "reason": "noUpdate" + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "none", + "reason": "noUpdate" + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "current-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "none", + "reason": "alreadyCurrent" + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "bad-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "none", + "reason": "rolledBack" + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "bad-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "bad-hash", + "attempts": [ + { + "type": "diff", + "urls": [ + "https://cdn.example.com/cur-next.hdiff", + "https://mirror.example.com/base/cur-next.hdiff" + ] + }, + { + "type": "pdiff", + "urls": [ + "https://cdn.example.com/pkg-next.phdiff", + "https://mirror.example.com/base/pkg-next.phdiff" + ] + }, + { + "type": "full", + "urls": [ + "https://cdn.example.com/next.ppk", + "https://mirror.example.com/base/next.ppk" + ] + } + ], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [ + { + "type": "diff", + "urls": [ + "https://cdn.example.com/cur-next.hdiff", + "https://mirror.example.com/base/cur-next.hdiff" + ] + }, + { + "type": "pdiff", + "urls": [ + "https://cdn.example.com/pkg-next.phdiff", + "https://mirror.example.com/base/pkg-next.phdiff" + ] + }, + { + "type": "full", + "urls": [ + "https://cdn.example.com/next.ppk", + "https://mirror.example.com/base/next.ppk" + ] + } + ], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [ + { + "type": "pdiff", + "urls": [ + "https://cdn.example.com/pkg-next.phdiff", + "https://mirror.example.com/base/pkg-next.phdiff" + ] + }, + { + "type": "full", + "urls": [ + "https://cdn.example.com/next.ppk", + "https://mirror.example.com/base/next.ppk" + ] + } + ], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "diff": "cur-next.hdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [ + { + "type": "diff", + "urls": [ + "https://cdn.example.com/cur-next.hdiff", + "https://mirror.example.com/base/cur-next.hdiff" + ] + }, + { + "type": "full", + "urls": [ + "https://cdn.example.com/next.ppk", + "https://mirror.example.com/base/next.ppk" + ] + } + ], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "full": "next.ppk" + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + false + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "full": "next.ppk", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + true + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [ + { + "type": "full", + "urls": [ + "https://cdn.example.com/next.ppk", + "https://mirror.example.com/base/next.ppk" + ] + } + ], + "devNoop": false + } + }, + { + "fn": "decideDownload", + "args": [ + { + "update": true, + "hash": "next-hash", + "diff": "cur-next.hdiff", + "pdiff": "pkg-next.phdiff", + "paths": [ + "cdn.example.com", + "https://mirror.example.com/base/" + ] + }, + { + "currentVersion": "current-hash", + "rolledBackVersion": "bad-hash" + }, + true + ], + "expected": { + "action": "download", + "hash": "next-hash", + "attempts": [], + "devNoop": true + } + } + ] +} diff --git a/cpp/update_flow_core/tests/update_flow_core_test.cpp b/cpp/update_flow_core/tests/update_flow_core_test.cpp new file mode 100644 index 00000000..66543c2e --- /dev/null +++ b/cpp/update_flow_core/tests/update_flow_core_test.cpp @@ -0,0 +1,101 @@ +// Replays the golden vectors generated by scripts/generate-flow-vectors.ts +// against the C++ port and compares canonical serializations. A mismatch +// means the two implementations of the decision layer disagree — fix the C++ +// side (the TS side is the reference) or regenerate stale vectors. +#include +#include +#include +#include + +#include "../flow_json.h" +#include "../update_flow_core.h" + +using flowjson::Parse; +using flowjson::Stringify; +using flowjson::Value; + +namespace { + +Value Dispatch(const std::string& fn, const Value& args, bool* known) { + *known = true; + if (fn == "murmurhash3_32_gc") { + return Value::Number(updateflow::Murmur3_32(args.At(0).AsString())); + } + if (fn == "isInRollout") { + return Value::Bool( + updateflow::IsInRollout(args.At(0).AsNumber(), args.At(1).AsString())); + } + if (fn == "joinUrls") { + return updateflow::JoinUrls(args.At(0), args.At(1)); + } + if (fn == "orderEndpointCandidates") { + return updateflow::OrderEndpointCandidates(args.At(0), + args.At(1).AsNumber()); + } + if (fn == "buildCheckRequestBody") { + return updateflow::BuildCheckRequestBody(args.At(0)); + } + if (fn == "resolveCheckResult") { + return updateflow::ResolveCheckResult(args.At(0), args.At(1)); + } + if (fn == "decideDownload") { + return updateflow::DecideDownload(args.At(0), args.At(1), + args.At(2).Truthy()); + } + *known = false; + return Value::Undefined(); +} + +} // namespace + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + std::ifstream file(argv[1]); + if (!file) { + std::fprintf(stderr, "cannot open %s\n", argv[1]); + return 2; + } + std::stringstream buffer; + buffer << file.rdbuf(); + + bool ok = false; + Value doc = Parse(buffer.str(), &ok); + if (!ok) { + std::fprintf(stderr, "failed to parse %s\n", argv[1]); + return 2; + } + + const Value& cases = doc.Get("cases"); + int failures = 0; + for (size_t i = 0; i < cases.Size(); i++) { + const Value& testCase = cases.At(i); + const std::string& fn = testCase.Get("fn").AsString(); + bool known = false; + Value actual = Dispatch(fn, testCase.Get("args"), &known); + if (!known) { + std::fprintf(stderr, "case %zu: unknown fn %s\n", i, fn.c_str()); + failures++; + continue; + } + std::string actualStr = Stringify(actual); + std::string expectedStr = Stringify(testCase.Get("expected")); + if (actualStr != expectedStr) { + std::fprintf(stderr, + "case %zu (%s) MISMATCH\n args: %s\n expected: %s\n" + " actual: %s\n", + i, fn.c_str(), Stringify(testCase.Get("args")).c_str(), + expectedStr.c_str(), actualStr.c_str()); + failures++; + } + } + + if (failures) { + std::fprintf(stderr, "%d of %zu vectors failed\n", failures, cases.Size()); + return 1; + } + std::printf("all %zu flow vectors passed\n", cases.Size()); + return 0; +} diff --git a/cpp/update_flow_core/update_flow_core.cpp b/cpp/update_flow_core/update_flow_core.cpp new file mode 100644 index 00000000..4488fdd8 --- /dev/null +++ b/cpp/update_flow_core/update_flow_core.cpp @@ -0,0 +1,264 @@ +#include "update_flow_core.h" + +#include + +namespace updateflow { + +using flowjson::Value; + +uint32_t Murmur3_32(const std::string& key, uint32_t seed) { + // The TS reference emulates 32-bit multiplication with 16-bit halves; + // native uint32_t arithmetic is exactly (x * y) mod 2^32, so the halved + // dance collapses to plain multiplies. Bytes are read as charCodeAt & 0xff, + // which is identical for the ASCII inputs this layer handles (uuids, keys). + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + const size_t len = key.size(); + const size_t nblocks = len / 4; + uint32_t h1 = seed; + + const unsigned char* data = + reinterpret_cast(key.data()); + for (size_t i = 0; i < nblocks; i++) { + uint32_t k1 = static_cast(data[i * 4]) | + (static_cast(data[i * 4 + 1]) << 8) | + (static_cast(data[i * 4 + 2]) << 16) | + (static_cast(data[i * 4 + 3]) << 24); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= c2; + h1 ^= k1; + h1 = (h1 << 13) | (h1 >> 19); + h1 = h1 * 5 + 0xe6546b64; + } + + uint32_t k1 = 0; + const unsigned char* tail = data + nblocks * 4; + switch (len & 3) { + case 3: + k1 ^= static_cast(tail[2]) << 16; + [[fallthrough]]; + case 2: + k1 ^= static_cast(tail[1]) << 8; + [[fallthrough]]; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= c2; + h1 ^= k1; + } + + h1 ^= static_cast(len); + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + return h1; +} + +bool IsInRollout(double rollout, const std::string& uuid) { + return static_cast(Murmur3_32(uuid) % 100) < rollout; +} + +namespace { + +// ^[a-z][a-z0-9+.-]*:\/\/ (case-insensitive) +bool HasExplicitScheme(const std::string& s) { + size_t i = 0; + auto isAlpha = [](char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + }; + if (i >= s.size() || !isAlpha(s[i])) { + return false; + } + i++; + while (i < s.size()) { + char c = s[i]; + if (isAlpha(c) || (c >= '0' && c <= '9') || c == '+' || c == '.' || + c == '-') { + i++; + } else { + break; + } + } + return s.compare(i, 3, "://") == 0; +} + +} // namespace + +Value JoinUrls(const Value& paths, const Value& fileName) { + if (!fileName.Truthy()) { + return Value::Undefined(); + } + Value urls = Value::Array(); + for (const auto& path : paths.elements()) { + std::string normalized = path.AsString(); + while (!normalized.empty() && normalized.back() == '/') { + normalized.pop_back(); + } + std::string base = HasExplicitScheme(normalized) + ? normalized + : "https://" + normalized; + urls.Push(Value::String(base + "/" + fileName.AsString())); + } + return urls; +} + +Value OrderEndpointCandidates(const Value& endpoints, double randomSample) { + Value deduped = Value::Array(); + for (const auto& endpoint : endpoints.elements()) { + if (!endpoint.Truthy()) { + continue; + } + bool seen = false; + for (const auto& kept : deduped.elements()) { + if (kept.AsString() == endpoint.AsString()) { + seen = true; + break; + } + } + if (!seen) { + deduped.Push(endpoint); + } + } + const size_t n = deduped.Size(); + if (n < 2) { + return deduped; + } + double idx = std::floor(randomSample * static_cast(n)); + size_t first = idx <= 0 ? 0 + : (idx >= static_cast(n - 1) + ? n - 1 + : static_cast(idx)); + Value ordered = Value::Array(); + ordered.Push(deduped.At(first)); + for (size_t i = 0; i < n; i++) { + if (i != first) { + ordered.Push(deduped.At(i)); + } + } + return ordered; +} + +Value BuildCheckRequestBody(const Value& input) { + Value body = Value::Object(); + body.Set("packageVersion", input.Get("packageVersion")); + body.Set("hash", input.Get("currentVersion")); + body.Set("buildTime", input.Get("buildTime")); + body.Set("cInfo", input.Get("cInfo")); + const Value& diffV = input.Get("supportedDiffVersion"); + if (diffV.Truthy()) { + body.Set("diffV", diffV); + } + const Value& bundleHash = input.Get("bundleHash"); + if (bundleHash.Truthy()) { + body.Set("bundleHash", bundleHash); + } + const Value& extra = input.Get("extra"); + if (extra.IsObject()) { + for (const auto& member : extra.members()) { + body.Set(member.first, member.second); + } + } + if (input.Get("isDev").Truthy()) { + body.Remove("buildTime"); + } + return body; +} + +Value ResolveCheckResult(const Value& rootInfo, const Value& identity) { + Value rootResult = Value::Object(); + for (const auto& member : rootInfo.members()) { + if (member.first != "expVersion") { + rootResult.Set(member.first, member.second); + } + } + const Value& expVersion = rootInfo.Get("expVersion"); + const Value& currentVersion = identity.Get("currentVersion"); + // expVersion?.config?.rollout?.[identity.packageVersion] — Get on a + // non-object returns Undefined, mirroring optional chaining. + const Value& rollout = expVersion.Get("config").Get("rollout").Get( + identity.Get("packageVersion").AsString()); + if (rootResult.Get("update").Truthy() && expVersion.Truthy() && + rollout.IsNumber()) { + if (IsInRollout(rollout.AsNumber(), identity.Get("uuid").AsString())) { + if (Value::StrictEquals(expVersion.Get("hash"), currentVersion)) { + Value upToDate = Value::Object(); + upToDate.Set("upToDate", Value::Bool(true)); + return upToDate; + } + Value info = Value::Object(); + info.Set("update", Value::Bool(true)); + for (const auto& member : expVersion.members()) { + info.Set(member.first, member.second); + } + if (rootResult.Get("paths").Truthy()) { + info.Set("paths", rootResult.Get("paths")); + } + return info; + } + } + if (rootResult.Get("update").Truthy() && + Value::StrictEquals(rootResult.Get("hash"), currentVersion)) { + Value upToDate = Value::Object(); + upToDate.Set("upToDate", Value::Bool(true)); + return upToDate; + } + return rootResult; +} + +namespace { + +Value DeclineDownload(const char* reason) { + Value none = Value::Object(); + none.Set("action", Value::String("none")); + none.Set("reason", Value::String(reason)); + return none; +} + +} // namespace + +Value DecideDownload(const Value& info, const Value& identity, bool isDev) { + const Value& hash = info.Get("hash"); + Value paths = info.Get("paths"); + if (paths.IsUndefined()) { + paths = Value::Array(); // const { paths = [] } — undefined only + } + if (!info.Get("update").Truthy() || !hash.Truthy()) { + return DeclineDownload("noUpdate"); + } + if (Value::StrictEquals(hash, identity.Get("currentVersion"))) { + return DeclineDownload("alreadyCurrent"); + } + const Value& rolledBack = identity.Get("rolledBackVersion"); + if (rolledBack.Truthy() && Value::StrictEquals(hash, rolledBack)) { + return DeclineDownload("rolledBack"); + } + Value attempts = Value::Array(); + auto pushAttempt = [&attempts](const char* type, const Value& urls) { + if (urls.IsArray() && urls.Size() > 0) { + Value attempt = Value::Object(); + attempt.Set("type", Value::String(type)); + attempt.Set("urls", urls); + attempts.Push(std::move(attempt)); + } + }; + if (!isDev) { + pushAttempt("diff", JoinUrls(paths, info.Get("diff"))); + pushAttempt("pdiff", JoinUrls(paths, info.Get("pdiff"))); + } + Value fullUrls = JoinUrls(paths, info.Get("full")); + pushAttempt("full", fullUrls); + + Value decision = Value::Object(); + decision.Set("action", Value::String("download")); + decision.Set("hash", hash); + decision.Set("attempts", std::move(attempts)); + decision.Set("devNoop", + Value::Bool(isDev && !(fullUrls.IsArray() && fullUrls.Size() > 0))); + return decision; +} + +} // namespace updateflow diff --git a/cpp/update_flow_core/update_flow_core.h b/cpp/update_flow_core/update_flow_core.h new file mode 100644 index 00000000..7c9fa689 --- /dev/null +++ b/cpp/update_flow_core/update_flow_core.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "flow_json.h" + +// C++ port of the update-flow decision layer. +// +// src/updateFlowCore.ts is the REFERENCE implementation: every function here +// mirrors its TS counterpart 1:1, including object key order and JS +// truthiness/strict-equality semantics, because parity is enforced by golden +// vectors (tests/flow_vectors.json, generated from the TS side). Any semantic +// change lands in the TS file first, regenerates the vectors, then gets +// ported here — never the other direction. +// +// Like the TS side this layer is pure: no IO, no time, no randomness — the +// random sample, identity and parsed JSON all arrive as parameters. The +// orchestrators (per-platform HTTP/download/state glue) own all effects. +namespace updateflow { + +uint32_t Murmur3_32(const std::string& key, uint32_t seed = 0); + +// murmur(uuid) % 100 < rollout — the gray-release bucketing predicate. +bool IsInRollout(double rollout, const std::string& uuid); + +// paths × fileName -> candidate URL array; Undefined when fileName is falsy +// (mirrors joinUrls returning undefined without a file name). +flowjson::Value JoinUrls(const flowjson::Value& paths, + const flowjson::Value& fileName); + +// Dedupe + move the sampled pick to the front, rest in configured order. +// randomSample ∈ [0, 1) is injected by the caller. +flowjson::Value OrderEndpointCandidates(const flowjson::Value& endpoints, + double randomSample); + +// input: { packageVersion, currentVersion, buildTime, cInfo, +// supportedDiffVersion?, bundleHash?, isDev?, extra? } +flowjson::Value BuildCheckRequestBody(const flowjson::Value& input); + +// identity: { packageVersion, currentVersion?, uuid } +flowjson::Value ResolveCheckResult(const flowjson::Value& rootInfo, + const flowjson::Value& identity); + +// identity: { currentVersion?, rolledBackVersion? } +// -> { action: 'none', reason } | { action: 'download', hash, attempts, +// devNoop } +flowjson::Value DecideDownload(const flowjson::Value& info, + const flowjson::Value& identity, bool isDev); + +} // namespace updateflow diff --git a/package.json b/package.json index cda5ff6d..8ebe60a8 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "submodule": "git submodule update --init --recursive", "test": "bun test src/__tests__", "test:patch-core": "./scripts/test-patch-core.sh", + "test:flow-core": "./scripts/test-update-flow-core.sh", + "generate:flow-vectors": "bun scripts/generate-flow-vectors.ts", "build:harmony-har": "node scripts/build-harmony-har.js", "build:harmony-e2e": "bash Example/e2etest/scripts/build-harmony-e2e.sh", "build:so": "bun submodule && bash scripts/build-android-so.sh", diff --git a/scripts/generate-flow-vectors.ts b/scripts/generate-flow-vectors.ts new file mode 100644 index 00000000..ea416bbe --- /dev/null +++ b/scripts/generate-flow-vectors.ts @@ -0,0 +1,265 @@ +// Golden-vector generator for the update-flow decision layer. +// +// src/updateFlowCore.ts is the reference implementation; this script runs it +// over a dense input matrix and writes the results to +// cpp/update_flow_core/tests/flow_vectors.json. The C++ port replays the same +// vectors in CI (scripts/test-update-flow-core.sh), and +// src/__tests__/flowVectors.test.ts fails whenever the TS implementation and +// the committed file disagree — so every semantic change must regenerate the +// vectors (bun scripts/generate-flow-vectors.ts) and keep both sides green. +// +// JSON cannot express `undefined`: an undefined return value is encoded by +// omitting `expected`, and undefined-valued object members disappear on +// serialization — the C++ side mirrors both (Kind::Undefined members are +// skipped by stringify). +import { + buildCheckRequestBody, + decideDownload, + isInRollout, + joinUrls, + murmurhash3_32_gc, + orderEndpointCandidates, + resolveCheckResult, +} from '../src/updateFlowCore'; + +const impls: Record any> = { + murmurhash3_32_gc, + isInRollout, + joinUrls, + orderEndpointCandidates, + buildCheckRequestBody, + resolveCheckResult, + decideDownload, +}; + +export interface FlowVector { + fn: string; + args: any[]; + expected?: any; +} + +export const buildVectors = (): FlowVector[] => { + const cases: FlowVector[] = []; + const add = (fn: string, ...args: any[]) => { + cases.push({ fn, args, expected: impls[fn](...args) }); + }; + + // murmurhash3_32_gc — canonical reference vectors + bucketing inputs + for (const key of [ + '', + 'hello', + 'test', + 'Hello, world!', + 'The quick brown fox jumps over the lazy dog', + 'test1', + 'test2', + 'test3', + '123e4567-e89b-12d3-a456-426614174000', + '123e4567-e89b-12d3-a456-426614174001', + 'a', + 'ab', + 'abc', + 'abcd', // every remainder-length path through the tail switch + ]) { + add('murmurhash3_32_gc', key); + } + + // isInRollout — boundaries around murmur('test1') % 100 === 62 + add('isInRollout', 63, 'test1'); + add('isInRollout', 62, 'test1'); + add('isInRollout', 61, 'test1'); + add('isInRollout', 0, 'test1'); + add('isInRollout', 100, 'test1'); + add('isInRollout', -1, 'test3'); + add('isInRollout', 54, 'test3'); + add('isInRollout', 53, 'test3'); + + // joinUrls + add('joinUrls', ['example.com']); // no fileName -> undefined + add('joinUrls', ['example.com'], ''); // falsy fileName -> undefined + add('joinUrls', [], 'file.txt'); + add('joinUrls', ['example.com', 'test.org'], 'file.txt'); + add('joinUrls', ['example.com///', 'http://example.com///'], 'file.txt'); + add('joinUrls', ['ftp://example.com', 'myapp://some/path'], 'file.txt'); + add('joinUrls', ['192.168.1.1:8080', '10.0.0.1:3000/api'], 'file.txt'); + add('joinUrls', [''], 'file.txt'); + add('joinUrls', ['HTTPS://Upper.example.com'], 'file.txt'); + add('joinUrls', ['a:b://weird'], 'file.txt'); // scheme regex must not match + + // orderEndpointCandidates + add('orderEndpointCandidates', ['a', 'b', 'c'], 0); + add('orderEndpointCandidates', ['a', 'b', 'c'], 0.34); + add('orderEndpointCandidates', ['a', 'b', 'c'], 0.5); + add('orderEndpointCandidates', ['a', 'b', 'c'], 0.99); + add('orderEndpointCandidates', ['a', 'b', 'c'], 1); // clamps to last + add('orderEndpointCandidates', ['a', null, 'a', '', 'b'], 0.6); + add('orderEndpointCandidates', [], 0.5); + add('orderEndpointCandidates', ['a'], 0.5); + + // buildCheckRequestBody + const cInfo = { rnu: '10.50.0', rn: '0.85.2', os: 'ios 17.5', uuid: 'u-1' }; + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + currentVersion: 'abcdef1234', + buildTime: '1719999999', + cInfo, + }); + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + currentVersion: 'abcdef1234', + buildTime: '1719999999', + cInfo, + supportedDiffVersion: 2, + bundleHash: 'a'.repeat(64), + }); + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + currentVersion: '', + buildTime: '1719999999', + cInfo, + supportedDiffVersion: 0, + bundleHash: '', + }); + // extra overrides keep the original key position (JS spread semantics) + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + currentVersion: 'abcdef1234', + buildTime: '1719999999', + cInfo, + extra: { toHash: 'debug-hash', hash: 'override-hash' }, + }); + // dev drops buildTime even when extra re-injects it + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + currentVersion: 'abcdef1234', + buildTime: '1719999999', + cInfo, + isDev: true, + extra: { buildTime: 'injected' }, + }); + // missing currentVersion: `hash` becomes undefined and vanishes on stringify + add('buildCheckRequestBody', { + packageVersion: '2.3.4', + buildTime: '1719999999', + cInfo, + }); + + // resolveCheckResult + const identity = { + packageVersion: '2.3.4', + currentVersion: 'current-hash', + uuid: 'test1', // bucket 62 + }; + const gray = (rollout: number, hash = 'gray-hash') => ({ + name: 'gray', + hash, + description: 'd', + metaInfo: 'm', + config: { rollout: { '2.3.4': rollout } }, + }); + const root = { + update: true, + hash: 'root-hash', + name: 'root', + description: 'rd', + metaInfo: 'rm', + diff: 'a.hdiff', + pdiff: 'b.phdiff', + full: 'c.ppk', + paths: ['cdn.example.com'], + }; + add('resolveCheckResult', { ...root, expVersion: gray(63) }, identity); + add('resolveCheckResult', { ...root, expVersion: gray(62) }, identity); + add( + 'resolveCheckResult', + { ...root, expVersion: gray(100, 'current-hash') }, + identity + ); + add('resolveCheckResult', { ...root }, identity); + add('resolveCheckResult', { ...root, hash: 'current-hash' }, identity); + add('resolveCheckResult', { upToDate: true }, identity); + add('resolveCheckResult', { update: false, hash: 'x' }, identity); + // rollout keyed by another packageVersion is ignored + add( + 'resolveCheckResult', + { + ...root, + expVersion: { ...gray(100), config: { rollout: { other: 100 } } }, + }, + identity + ); + // in-rollout target without root paths: nothing inherited + add( + 'resolveCheckResult', + { update: true, hash: 'root-hash', expVersion: gray(63) }, + identity + ); + // strict-equality edge: both hashes undefined -> upToDate + add( + 'resolveCheckResult', + { + update: true, + hash: 'root-hash', + expVersion: { ...gray(63), hash: undefined }, + }, + { packageVersion: '2.3.4', uuid: 'test1' } + ); + + // decideDownload + const dlIdentity = { + currentVersion: 'current-hash', + rolledBackVersion: 'bad-hash', + }; + const dlInfo = { + update: true, + hash: 'next-hash', + diff: 'cur-next.hdiff', + pdiff: 'pkg-next.phdiff', + full: 'next.ppk', + paths: ['cdn.example.com', 'https://mirror.example.com/base/'], + }; + add('decideDownload', { upToDate: true }, dlIdentity, false); + add('decideDownload', { update: true }, dlIdentity, false); + add('decideDownload', { ...dlInfo, hash: 'current-hash' }, dlIdentity, false); + add('decideDownload', { ...dlInfo, hash: 'bad-hash' }, dlIdentity, false); + // no rolledBackVersion recorded: same hash must NOT be declined + add( + 'decideDownload', + { ...dlInfo, hash: 'bad-hash' }, + { currentVersion: 'current-hash' }, + false + ); + add('decideDownload', dlInfo, dlIdentity, false); + add('decideDownload', { ...dlInfo, diff: undefined }, dlIdentity, false); + add('decideDownload', { ...dlInfo, pdiff: undefined }, dlIdentity, false); + add( + 'decideDownload', + { ...dlInfo, diff: undefined, pdiff: undefined, full: undefined }, + dlIdentity, + false + ); + add('decideDownload', { ...dlInfo, paths: [] }, dlIdentity, false); + add( + 'decideDownload', + { update: true, hash: 'next-hash', full: 'next.ppk' }, + dlIdentity, + false + ); // paths defaulted to [] + add('decideDownload', dlInfo, dlIdentity, true); // dev: full only + add('decideDownload', { ...dlInfo, full: undefined }, dlIdentity, true); // devNoop + + return cases; +}; + +if (import.meta.main) { + const outPath = new URL( + '../cpp/update_flow_core/tests/flow_vectors.json', + import.meta.url + ).pathname; + const doc = { + generated_by: 'scripts/generate-flow-vectors.ts — do not edit by hand', + cases: buildVectors(), + }; + await Bun.write(outPath, `${JSON.stringify(doc, null, 2)}\n`); + console.log(`wrote ${doc.cases.length} vectors to ${outPath}`); +} diff --git a/scripts/test-update-flow-core.sh b/scripts/test-update-flow-core.sh new file mode 100755 index 00000000..47c955c3 --- /dev/null +++ b/scripts/test-update-flow-core.sh @@ -0,0 +1,28 @@ +#!/bin/sh +set -eu + +ROOT_DIR="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)" +BUILD_DIR="$ROOT_DIR/.tmp/update-flow-core-tests" + +mkdir -p "$BUILD_DIR" + +# Opt-in sanitizers, same convention as test-patch-core.sh: +# SANITIZE=1 npm run test:flow-core +SANITIZE_FLAGS="" +if [ "${SANITIZE:-0}" = "1" ]; then + SANITIZE_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" + echo "Building update flow core tests with AddressSanitizer + UBSan" +fi + +c++ \ + -std=c++17 \ + -Wall \ + -Wextra \ + $SANITIZE_FLAGS \ + "$ROOT_DIR/cpp/update_flow_core/flow_json.cpp" \ + "$ROOT_DIR/cpp/update_flow_core/update_flow_core.cpp" \ + "$ROOT_DIR/cpp/update_flow_core/tests/update_flow_core_test.cpp" \ + -o "$BUILD_DIR/update_flow_core_test" + +"$BUILD_DIR/update_flow_core_test" \ + "$ROOT_DIR/cpp/update_flow_core/tests/flow_vectors.json" diff --git a/src/__tests__/flowVectors.test.ts b/src/__tests__/flowVectors.test.ts new file mode 100644 index 00000000..35326a53 --- /dev/null +++ b/src/__tests__/flowVectors.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from 'bun:test'; +import vectorsFile from '../../cpp/update_flow_core/tests/flow_vectors.json'; +import { buildVectors } from '../../scripts/generate-flow-vectors'; + +describe('flow golden vectors', () => { + // The committed vectors are the parity contract between src/updateFlowCore.ts + // (reference) and cpp/update_flow_core (port, replayed by test:flow-core). + // A semantic change to the TS side must regenerate the file + // (bun scripts/generate-flow-vectors.ts) AND keep the C++ side green — + // this test catches the half-done state. + test('committed vectors match the TS reference implementation', () => { + // JSON round-trip applies the same undefined-dropping normalization the + // generator's serialization does. + expect(JSON.parse(JSON.stringify(buildVectors()))).toEqual( + vectorsFile.cases + ); + }); +}); From b945d0bef6c49b6ddce883fb345f3e7edbbe4572 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 22:43:42 +0800 Subject: [PATCH 06/29] =?UTF-8?q?docs:=20final=20verdict=20=E2=80=94=20opt?= =?UTF-8?q?ion=20B,=20uniform=20C++=20decision=20layer=20on=20all=20platfo?= =?UTF-8?q?rms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the reasoning chain that closes the runtime-selection question: engines were only ever a means to a single source of truth; with the host Hermes ruled out and javascriptengine only viable as a runtime-conditional optimization, an always-available C++ implementation must exist on Android anyway, at which point uniform C++ beats both the hybrid (two mechanisms) and QuickJS-everywhere (200KB x 4 to save one mechanical, vector-guarded edit). The single-source property is preserved as "one semantics, mechanically enforced" via the golden-vector contract. The guardian chapter is demoted to a rejected-route record; remaining work is §8 step 4 (native orchestration). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 45 ++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 59de3e7b..c81e62fe 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -1,6 +1,8 @@ # 原生检测更新设计:让砖机也能被修好 -> 状态:实施中——§8 第 2 步(纯函数抽取)已完成(`src/updateFlowCore.ts`,2026-08-07) +> 状态:实施中——选型已改判方案 B(三端统一 C++ + 金标向量契约,见 §4 裁决)。 +> 决策层双实现已落地:`src/updateFlowCore.ts`(参照)+ `cpp/update_flow_core` +> (移植),69 条向量双侧钉死。剩余工作:§8 第 4 步原生编排。 > 取代:`REMOTE_RESET_DESIGN.md`(本地启动熔断方案,已放弃,理由见 §1.2) > 前置:bundleHash 迁移 Phase 0/1/2 已上线且 buildTime 永久保留作 fallback 已定稿 > (2026-08-03)——checkUpdate 的 wire protocol 已稳定,协议前置解除 @@ -92,9 +94,26 @@ A 直接淘汰。B 是 [[native-update-reset-design]] 里 Phase 3 的原计划 瘫"。真正集中风险的是原生**编排**代码(R2),而那部分 B / C 完全相同, guardian 救不了它。 +> **最终裁决(2026-08-07):选 B,三端统一 C++,金标向量作为双实现的强制契 +> 约(已实现)。** 推理链:引擎方案的全部价值 = 单一事实来源;Android 宿主 +> Hermes 被符号侦察排除、javascriptengine 因可用性是运行时属性只能当可选优 +> 化——一个必定可用的 C++ 实现无论如何要存在;一旦如此,混合形态(iOS/ +> Harmony 求值 + Android C++)要养两套机制,统一 C++ 的改动次数相同却删掉了 +> 求值器机制整个类别;QuickJS 统一保单源要付 200KB×4 + 三端引擎集成,只换 +> 来"少改一处代码",而那处改动有向量护栏,是机械劳动。 +> "单源"要保的性质是**语义唯一且被机械强制**,由金标向量提供: +> `src/updateFlowCore.ts` 是参照实现(oracle), +> `scripts/generate-flow-vectors.ts` 产出 +> `cpp/update_flow_core/tests/flow_vectors.json`(69 向量), +> `src/__tests__/flowVectors.test.ts` 钉住 TS 与向量文件一致, +> `scripts/test-update-flow-core.sh`(CI cpp-test job,带 ASan/UBSan)钉住 +> C++ 移植与向量一致。**纪律:语义改动先落 TS → 重新生成向量 → 移植 C++, +> 两侧不绿不发版。** C++ 侧的 `flow_json` 按 JS 语义实现(插入序对象、 +> undefined ≠ null、JS truthiness / 严格相等),移植才能逐字节对齐。 + --- -## 5. Guardian bundle +## 5. Guardian bundle(已否决路线,留档防止重新发明;裁决见 §4) ### 5.1 关键设计约束:它必须是纯函数,不做 IO @@ -256,16 +275,18 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示 `isInRollout` / `joinUrls` / `orderEndpointCandidates`,无 IO、无 react-native 依赖、无模块级状态(身份/随机数均参数注入),import 闭包 为纯;client.ts / provider.tsx / endpoint.ts 已原地改用,单测覆盖 -3. **运行时选型** —— 源码可移植性已验证(2026-08-07,三裸引擎逐字节一 - 致,见 §5.2);iOS 定为系统 JavaScriptCore、Harmony 定为系统 JSVM-API, - **剩余 spike 仅 Android**(javascriptengine / Hermes 链接 / QuickJS 三 - 选一,全不干净则 Android 单端退回 C++ 实现的混合形态) -4. **原生编排** —— 三端 HTTP + 调用纯函数 + 复用现有下载/patch/state; - endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race -5. ~~**guardian 分发通道**~~ **已裁决不做**(2026-08-07,见 §5.3)—— - guardian 只带基线、无远程覆盖;砖机救援能力在第 4 步,不受影响 - -第 2 步曾是关键路径(B 和 C 的公共前置),现已完成——下一个决策点是第 3 步的求值引擎验证,其结论裁决"决策层用 JS 源码求值还是 C++ 重写"(第 4 步两个结果都要做,路线不再分叉)。 +3. ~~**运行时选型**~~ **已关闭:改判方案 B**(2026-08-07,裁决与推理链见 + §4)。`cpp/update_flow_core` 已实现(flow_json + 七个决策函数的 1:1 移 + 植),69 条金标向量在本机与 CI(ASan/UBSan)全过 +4. **原生编排** —— 三端 HTTP + 调用 `update_flow_core` + 复用现有下载/ + patch/state;endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race。 + Android 侧 `update_flow_core` 进 `librnupdate.so`(协议演进从此绑定 + `.so` 重编,走 build-android-so.sh + CI + verify-android-so.js 的 16KB + 对齐断言) +5. ~~**guardian 分发通道**~~ **已裁决不做**(2026-08-07,见 §5.3)——随 + §4 改判 B,整个 guardian 路线关闭;砖机救援能力在第 4 步,不受影响 + +剩余工作只有第 4 步:原生编排。决策层已就位(TS 参照 + C++ 移植 + 向量契约),编排层是纯 IO 胶水。 ## 9. 已写代码的处置 From a8710f92beb27f3600ae4be3dfc07af194d3b4fe Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:01:49 +0800 Subject: [PATCH 07/29] fix: harden the flow_json parser against hostile server input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parser also consumes checkUpdate responses, so malformed input must fail cleanly: recursive descent is now capped at 64 nesting levels (a 100k-bracket bomb previously walked straight toward the stack limit), and the test binary gains a robustness suite — 20 malformed inputs that must set ok=false without crashing (verified under ASan+UBSan in CI), plus tricky-but-valid cases pinning JSON.parse semantics the port relies on (duplicate keys keep first position with last value, surrogate-pair decoding, canonical number reserialization). Co-Authored-By: Claude Fable 5 --- cpp/update_flow_core/flow_json.cpp | 19 +++- cpp/update_flow_core/flow_json.h | 3 +- .../tests/update_flow_core_test.cpp | 103 +++++++++++++++++- 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/cpp/update_flow_core/flow_json.cpp b/cpp/update_flow_core/flow_json.cpp index 89c1ad03..dfa90668 100644 --- a/cpp/update_flow_core/flow_json.cpp +++ b/cpp/update_flow_core/flow_json.cpp @@ -204,6 +204,11 @@ std::string Stringify(const Value& v) { namespace { +// The parser also consumes server checkUpdate responses, so hostile input +// must fail cleanly: nesting is capped to keep recursive descent off the +// stack limit. Real responses nest ~4 levels. +constexpr int kMaxDepth = 64; + class Parser { public: Parser(const std::string& text) : text_(text) {} @@ -258,9 +263,16 @@ class Parser { char c = text_[pos_]; switch (c) { case '{': - return ParseObject(); - case '[': - return ParseArray(); + case '[': { + if (depth_ >= kMaxDepth) { + ok_ = false; + return Value::Undefined(); + } + depth_++; + Value v = c == '{' ? ParseObject() : ParseArray(); + depth_--; + return v; + } case '"': return Value::String(ParseString()); case 't': @@ -458,6 +470,7 @@ class Parser { const std::string& text_; size_t pos_ = 0; + int depth_ = 0; bool ok_ = true; }; diff --git a/cpp/update_flow_core/flow_json.h b/cpp/update_flow_core/flow_json.h index 84947ea2..09a5ccba 100644 --- a/cpp/update_flow_core/flow_json.h +++ b/cpp/update_flow_core/flow_json.h @@ -96,7 +96,8 @@ class Value { std::string Stringify(const Value& v); // Strict JSON parser (the vectors file and checkUpdate responses). Returns -// Undefined and sets *ok to false on malformed input. +// Undefined and sets *ok to false on malformed input; nesting beyond 64 +// levels is rejected so hostile server data cannot exhaust the stack. Value Parse(const std::string& text, bool* ok); } // namespace flowjson diff --git a/cpp/update_flow_core/tests/update_flow_core_test.cpp b/cpp/update_flow_core/tests/update_flow_core_test.cpp index 66543c2e..fb0c7106 100644 --- a/cpp/update_flow_core/tests/update_flow_core_test.cpp +++ b/cpp/update_flow_core/tests/update_flow_core_test.cpp @@ -46,6 +46,101 @@ Value Dispatch(const std::string& fn, const Value& args, bool* known) { return Value::Undefined(); } +// The parser consumes network data (checkUpdate responses); malformed and +// hostile inputs must set ok=false without crashing — this suite runs under +// ASan+UBSan in CI. Valid-but-tricky inputs pin the JS JSON.parse semantics +// the port relies on. +int RunParserRobustness() { + int failures = 0; + + const char* malformed[] = { + "", + "{", + "[1,2", + "\"abc", + "{\"a\":}", + "{\"a\" 1}", + "{\"a\":1,}", + "[1,]", + "tru", + "nul", + "falsy", + "1x", + "1 2", + "- 1", + "\"\\q\"", + "\"\\u12\"", + "\"\\ud800\\u0041\"", // high surrogate followed by a non-low escape + "{\"a\":1}garbage", + "[}", + "{]", + }; + for (const char* input : malformed) { + bool ok = true; + Parse(input, &ok); + if (ok) { + std::fprintf(stderr, "robustness: accepted malformed input: %s\n", input); + failures++; + } + } + + // Nesting: 64 levels parse, 65 are rejected (stack-depth cap). + for (int depth : {64, 65}) { + std::string nested; + for (int i = 0; i < depth; i++) { + nested.push_back('['); + } + for (int i = 0; i < depth; i++) { + nested.push_back(']'); + } + bool ok = false; + Parse(nested, &ok); + bool expected = depth <= 64; + if (ok != expected) { + std::fprintf(stderr, "robustness: depth %d parsed=%d, expected %d\n", + depth, ok, expected); + failures++; + } + } + // A deeply nested bomb must fail cleanly, not overflow the stack. + { + std::string bomb(100000, '['); + bool ok = true; + Parse(bomb, &ok); + if (ok) { + std::fprintf(stderr, "robustness: accepted nesting bomb\n"); + failures++; + } + } + + // Valid-but-tricky inputs: assert the parsed value via canonical stringify. + const struct { + const char* input; + const char* expected; + } tricky[] = { + // Duplicate keys: first position, last value (JSON.parse semantics). + {"{\"a\":1,\"b\":2,\"a\":3}", "{\"a\":3,\"b\":2}"}, + {"\"\\u00e9\"", "\"\xc3\xa9\""}, // BMP escape -> UTF-8 + {"\"\\ud83d\\ude00\"", "\"\xf0\x9f\x98\x80\""}, // surrogate pair + {" { \"a\" : [ 1 , true , null ] } ", "{\"a\":[1,true,null]}"}, + {"-0.5", "-0.5"}, + {"1e2", "100"}, + }; + for (const auto& t : tricky) { + bool ok = false; + Value v = Parse(t.input, &ok); + std::string actual = Stringify(v); + if (!ok || actual != t.expected) { + std::fprintf(stderr, + "robustness: %s -> ok=%d %s (expected %s)\n", t.input, ok, + actual.c_str(), t.expected); + failures++; + } + } + + return failures; +} + } // namespace int main(int argc, char* argv[]) { @@ -69,7 +164,7 @@ int main(int argc, char* argv[]) { } const Value& cases = doc.Get("cases"); - int failures = 0; + int failures = RunParserRobustness(); for (size_t i = 0; i < cases.Size(); i++) { const Value& testCase = cases.At(i); const std::string& fn = testCase.Get("fn").AsString(); @@ -93,9 +188,11 @@ int main(int argc, char* argv[]) { } if (failures) { - std::fprintf(stderr, "%d of %zu vectors failed\n", failures, cases.Size()); + std::fprintf(stderr, "%d failures (%zu vectors + parser robustness)\n", + failures, cases.Size()); return 1; } - std::printf("all %zu flow vectors passed\n", cases.Size()); + std::printf("all %zu flow vectors + parser robustness passed\n", + cases.Size()); return 0; } From 9a0d113b9c95d9b42313d3aa388abed1476406ed Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:13:39 +0800 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20HandleCheckResponse=20composition?= =?UTF-8?q?=20+=20native=20orchestration=20design=20(=C2=A710)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HandleCheckResponse composes Parse → ResolveCheckResult → DecideDownload so platform orchestrators go from raw response text to a download decision (with the resolved info attached for setLocalHashInfo) in one call, containing zero decision logic; covered by nine composition cases in the C++ test suite under ASan+UBSan. The design doc gains §10: the provisioning gap surfaced by step 4 — JS is the single config source, syncNativeConfig persists {appKey, endpoints, queryUrls, afterDownload, rnu, rn} and missing config means the native check silently does not run (which doubles as the rollout gate); the cold-start flow, JS result-reuse handoff, bounded failure policy (no retry storms, no version blacklisting — the local-watchdog lesson), and the per-platform landing order (iOS first). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 67 +++++++++++++++ .../tests/update_flow_core_test.cpp | 82 ++++++++++++++++++- cpp/update_flow_core/update_flow_core.cpp | 13 +++ cpp/update_flow_core/update_flow_core.h | 12 +++ 4 files changed, 173 insertions(+), 1 deletion(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index c81e62fe..165f7f2e 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -296,3 +296,70 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示 - `autoReset` 遥测事件 —— 无 reset 动作后失去意义,撤回 `unconfirmedBoots` / 前台闸门 / `markBootHealthy` / `PROVIDER_REQUIRED` / `CONTENT_APPEARED` 全部撤回。 + +--- + +## 10. 原生编排设计(§8 第 4 步) + +### 10.1 标定(provisioning)——设计到此才暴露的缺口 + +appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` 里,而原 +生检测跑在冷启动、任何 JS 之前。解决:**JS 是唯一配置源,原生只消费落盘副 +本。** 每次 `setOptions`(含构造)后 JS 调新的原生方法 `syncNativeConfig`, +持久化: + +``` +{ appKey, endpoints: server.main, queryUrls, afterDownload: 'none' | 'setNeedUpdate', + disabled?: boolean, rnu, rn } +``` + +- `afterDownload` 由 updateStrategy 折算:`silentAndLater` / `silentAndNow` + → `setNeedUpdate`(原生的作用面本来就是"下次启动");alert 类策略 → 只下 + 载不激活,弹窗与确认永远归 JS(§6) +- **无配置 → 原生静默不跑**。首次安装首启、或从未升级到新 JS 的老接入,天 + 然回到现状,零行为变化——这就是灰度开关,不需要另设开关 +- 刻意不做 Info.plist / AndroidManifest 注入:配置双源必然漂移 +- 砖机场景自洽:设备能被坏热更砖掉,说明它至少健康跑过一次 JS,配置早已落盘 + +### 10.2 流程 + +冷启动 + 延迟 5s(R5),后台线程,每次冷启动至多一轮: + +``` +读 config(无则退出)+ 原生 state(currentVersion / rolledBackVersion / + packageVersion / buildTime / uuid / supportedDiffVersion / bundleHash 缓存) +→ BuildCheckRequestBody(bundleHash 同步读缓存,缺省省略字段) +→ OrderEndpointCandidates(endpoints, 原生随机数) +→ 顺序请求,单个超时 10s;全失败 → 拉 queryUrls(任一成功即用)合并新候选, + 排除已失败的,再顺序一轮;仍失败 → 本轮放弃 +→ HandleCheckResponse(响应原文, identity, isDev=false)(已实现,含 info 透出) +→ action=download:按 attempts 顺序走现有下载器(diff→pdiff→full, + testUrls 语义由原生逐个尝试实现);成功 → setLocalHashInfo(info 的 + name/description/metaInfo) → 按 afterDownload 决定是否 setNeedUpdate +→ 响应原文 + 时间戳落盘(§10.3) +``` + +### 10.3 与 JS 的去重(§6 的落地形态) + +首版**原生只写缓存**:响应原文 + 时间戳落到固定文件。紧随其后的 JS 小改 +动:`checkUpdate` 先读该缓存,时间戳新鲜(暂定 2 分钟)则直接复用不发请 +求。改造前的过渡期是双检查——多一次网络请求,服务端有缓存,无害。 + +### 10.4 失败策略 + +- 整轮静默失败:无重试风暴、无退避状态机,下次冷启动天然重试 +- 下载/patch 失败不拉黑版本、不计数(本地熔断的教训:多记会毁好版本); + 每次启动至多重试一轮,行为有界 +- 不引入任何新的回退/降级路径;apk 过期(expired)响应原生不处理,留给 JS UI + +### 10.5 安全面 + +与 JS 路径同一协议、同一 HTTPS endpoints、同一下载器 hash 校验,无新增 +面。`flow_json` 解析网络数据已做深度上限 + 畸形输入回归(ASan/UBSan)。 + +### 10.6 分平台落地顺序 + +iOS(NSURLSession,下载/patch/state 全现成,纯增量)→ Android(OkHttp + +librnupdate.so 进 update_flow_core,绑一次 .so 重编)→ Harmony。e2e 用例 +沿用 Example/e2etest 既有基建,验证"坏 bundle 下原生仍能拉到修复版"的端到 +端场景。 diff --git a/cpp/update_flow_core/tests/update_flow_core_test.cpp b/cpp/update_flow_core/tests/update_flow_core_test.cpp index fb0c7106..0913eccd 100644 --- a/cpp/update_flow_core/tests/update_flow_core_test.cpp +++ b/cpp/update_flow_core/tests/update_flow_core_test.cpp @@ -141,6 +141,86 @@ int RunParserRobustness() { return failures; } +// HandleCheckResponse is pure composition (no decision logic of its own), so +// it is tested directly here instead of via TS-generated vectors: response +// text in, canonical decision out. +int RunHandleCheckResponse() { + int failures = 0; + Value identity = Value::Object(); + identity.Set("packageVersion", Value::String("2.3.4")); + identity.Set("currentVersion", Value::String("cur")); + identity.Set("uuid", Value::String("test1")); // bucket 62 + identity.Set("rolledBackVersion", Value::String("bad")); + + const struct { + const char* name; + const char* response; + const char* expected; + } cases[] = { + {"malformed", "not json{", + "{\"action\":\"none\",\"reason\":\"invalidResponse\"}"}, + {"non-object", "[1,2]", + "{\"action\":\"none\",\"reason\":\"invalidResponse\"}"}, + {"upToDate", "{\"upToDate\":true}", + "{\"action\":\"none\",\"reason\":\"noUpdate\",\"info\":{\"upToDate\":" + "true}}"}, + {"expired", + "{\"expired\":true,\"downloadUrl\":\"https://x/app.apk\"}", + "{\"action\":\"none\",\"reason\":\"noUpdate\",\"info\":{\"expired\":" + "true,\"downloadUrl\":\"https://x/app.apk\"}}"}, + {"alreadyCurrent", + "{\"update\":true,\"hash\":\"cur\",\"full\":\"cur.ppk\"," + "\"paths\":[\"cdn.x.com\"]}", + "{\"action\":\"none\",\"reason\":\"noUpdate\",\"info\":{\"upToDate\":" + "true}}"}, + {"download", + "{\"update\":true,\"hash\":\"h2\",\"full\":\"h2.ppk\"," + "\"paths\":[\"cdn.x.com\"],\"name\":\"v2\"}", + "{\"action\":\"download\",\"hash\":\"h2\",\"attempts\":[{\"type\":" + "\"full\",\"urls\":[\"https://cdn.x.com/h2.ppk\"]}],\"devNoop\":false," + "\"info\":{\"update\":true,\"hash\":\"h2\",\"full\":\"h2.ppk\"," + "\"paths\":[\"cdn.x.com\"],\"name\":\"v2\"}}"}, + {"rollout-in", + "{\"update\":true,\"hash\":\"root\",\"full\":\"root.ppk\"," + "\"paths\":[\"cdn.x.com\"],\"expVersion\":{\"name\":\"g\",\"hash\":" + "\"gray\",\"full\":\"gray.ppk\",\"config\":{\"rollout\":{\"2.3.4\":" + "63}}}}", + "{\"action\":\"download\",\"hash\":\"gray\",\"attempts\":[{\"type\":" + "\"full\",\"urls\":[\"https://cdn.x.com/gray.ppk\"]}],\"devNoop\":" + "false,\"info\":{\"update\":true,\"name\":\"g\",\"hash\":\"gray\"," + "\"full\":\"gray.ppk\",\"config\":{\"rollout\":{\"2.3.4\":63}}," + "\"paths\":[\"cdn.x.com\"]}}"}, + {"rollout-out", + "{\"update\":true,\"hash\":\"root\",\"full\":\"root.ppk\"," + "\"paths\":[\"cdn.x.com\"],\"expVersion\":{\"name\":\"g\",\"hash\":" + "\"gray\",\"full\":\"gray.ppk\",\"config\":{\"rollout\":{\"2.3.4\":" + "62}}}}", + "{\"action\":\"download\",\"hash\":\"root\",\"attempts\":[{\"type\":" + "\"full\",\"urls\":[\"https://cdn.x.com/root.ppk\"]}],\"devNoop\":" + "false,\"info\":{\"update\":true,\"hash\":\"root\",\"full\":" + "\"root.ppk\",\"paths\":[\"cdn.x.com\"]}}"}, + {"rolledBack", + "{\"update\":true,\"hash\":\"bad\",\"full\":\"bad.ppk\"," + "\"paths\":[\"cdn.x.com\"]}", + "{\"action\":\"none\",\"reason\":\"rolledBack\",\"info\":{\"update\":" + "true,\"hash\":\"bad\",\"full\":\"bad.ppk\",\"paths\":[\"cdn.x.com\"]}" + "}"}, + }; + for (const auto& c : cases) { + Value decision = + updateflow::HandleCheckResponse(c.response, identity, false); + std::string actual = Stringify(decision); + if (actual != c.expected) { + std::fprintf(stderr, + "handleCheckResponse %s MISMATCH\n expected: %s\n" + " actual: %s\n", + c.name, c.expected, actual.c_str()); + failures++; + } + } + return failures; +} + } // namespace int main(int argc, char* argv[]) { @@ -164,7 +244,7 @@ int main(int argc, char* argv[]) { } const Value& cases = doc.Get("cases"); - int failures = RunParserRobustness(); + int failures = RunParserRobustness() + RunHandleCheckResponse(); for (size_t i = 0; i < cases.Size(); i++) { const Value& testCase = cases.At(i); const std::string& fn = testCase.Get("fn").AsString(); diff --git a/cpp/update_flow_core/update_flow_core.cpp b/cpp/update_flow_core/update_flow_core.cpp index 4488fdd8..6ec53a0b 100644 --- a/cpp/update_flow_core/update_flow_core.cpp +++ b/cpp/update_flow_core/update_flow_core.cpp @@ -261,4 +261,17 @@ Value DecideDownload(const Value& info, const Value& identity, bool isDev) { return decision; } +Value HandleCheckResponse(const std::string& responseText, + const Value& identity, bool isDev) { + bool ok = false; + Value root = flowjson::Parse(responseText, &ok); + if (!ok || !root.IsObject()) { + return DeclineDownload("invalidResponse"); + } + Value resolved = ResolveCheckResult(root, identity); + Value decision = DecideDownload(resolved, identity, isDev); + decision.Set("info", std::move(resolved)); + return decision; +} + } // namespace updateflow diff --git a/cpp/update_flow_core/update_flow_core.h b/cpp/update_flow_core/update_flow_core.h index 7c9fa689..3d42759f 100644 --- a/cpp/update_flow_core/update_flow_core.h +++ b/cpp/update_flow_core/update_flow_core.h @@ -48,4 +48,16 @@ flowjson::Value ResolveCheckResult(const flowjson::Value& rootInfo, flowjson::Value DecideDownload(const flowjson::Value& info, const flowjson::Value& identity, bool isDev); +// Composes Parse → ResolveCheckResult → DecideDownload: one call from the +// raw checkUpdate response text to a download decision, so the platform +// orchestrators contain no decision logic at all. identity is the union of +// both composed functions' needs: { packageVersion, currentVersion?, uuid, +// rolledBackVersion? }. The decision additionally carries `info` — the +// resolved check result — so orchestrators can persist name/description/ +// metaInfo alongside a downloaded version (the JS side's setLocalHashInfo). +// Malformed JSON yields { action: 'none', reason: 'invalidResponse' }. +flowjson::Value HandleCheckResponse(const std::string& responseText, + const flowjson::Value& identity, + bool isDev); + } // namespace updateflow From e34f390d34c4df615fdfe061c162fa778dab24ad Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:27:25 +0800 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20syncNativeConfig=20=E2=80=94=20pr?= =?UTF-8?q?ovisioning=20layer=20for=20the=20native=20cold-start=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First brick of NATIVE_CHECKUPDATE_DESIGN §10: the native check runs before any JS, so its config must already be on disk. JS is the single config source — after every setOptions the client persists {appKey, endpoints, queryUrls, afterDownload, rnu, rn} through the new syncNativeConfig native method (feature-detected, fire-and-forget, skipped on web). afterDownload folds updateStrategy: silent strategies let the native side activate a downloaded version for the next launch, alert strategies keep activation with JS. Absent persisted config disables the native check entirely, which doubles as the rollout gate: old integrations and first launches see zero behavior change, and shipping this ahead of the orchestrator lets devices accumulate config in the field. All three platforms store the raw JSON (parsed on read by the future orchestrator) and validate it at write time — a corrupt config would otherwise silently disable the native check with no signal. Verified: bun test (155) + tsc + biome, harmony tsc against the DevEco SDK, clang -fsyntax-only for RCTPushy.mm against e2e Pods, javac of main+oldarch against the RN 0.85 AAR (newarch mirrors setUuid; covered by CI codegen builds). Co-Authored-By: Claude Fable 5 --- .../modules/update/UpdateModuleImpl.java | 31 ++++++++++ .../modules/update/UpdateModule.java | 5 ++ .../modules/update/UpdateModule.java | 5 ++ .../pushy/src/main/ets/PushyTurboModule.ts | 11 ++++ ios/RCTPushy/RCTPushy.mm | 27 +++++++++ src/NativePushy.ts | 8 +++ src/__tests__/client.test.ts | 56 +++++++++++++++++++ src/client.ts | 42 ++++++++++++++ 8 files changed, 185 insertions(+) diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java index ccfbea6b..6840c931 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java @@ -228,6 +228,37 @@ private static void setUuidInternal(UpdateContext updateContext, String uuid) { updateContext.setKv("uuid", uuid); } + /** + * Provisioning for the native cold-start update check + * (NATIVE_CHECKUPDATE_DESIGN §10.1): the raw JSON persists as-is and is + * parsed on read by the orchestrator; absent config = check disabled. + * Validated at write time — a corrupt config would otherwise silently + * disable the native check forever with no signal. + */ + public static void syncNativeConfig( + final UpdateContext updateContext, + final String config, + final Promise promise + ) { + if (config == null || config.isEmpty()) { + promise.reject(ErrorCodes.INVALID_OPTIONS, "config must be a JSON object string"); + return; + } + try { + new JSONObject(config); + } catch (JSONException e) { + promise.reject(ErrorCodes.INVALID_OPTIONS, "config must be a JSON object string", e); + return; + } + StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "syncNativeConfig", new StateSerialRunner.Operation() { + @Override + public void run() { + updateContext.setKv("nativeConfig", config); + promise.resolve(true); + } + }); + } + public static void setUuid( final UpdateContext updateContext, final String uuid, diff --git a/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java b/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java index 703a444c..1ddea874 100644 --- a/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +++ b/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java @@ -88,6 +88,11 @@ public void setUuid(String uuid, Promise promise) { UpdateModuleImpl.setUuid(updateContext, uuid, promise); } + @Override + public void syncNativeConfig(String config, Promise promise) { + UpdateModuleImpl.syncNativeConfig(updateContext, config, promise); + } + @Override public void setLocalHashInfo(String hash, String info, Promise promise) { UpdateModuleImpl.setLocalHashInfo(updateContext, hash, info, promise); diff --git a/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java b/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java index e3bc8068..e21177c3 100644 --- a/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +++ b/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java @@ -90,6 +90,11 @@ public void setUuid(String uuid) { UpdateModuleImpl.setUuid(updateContext, uuid); } + @ReactMethod + public void syncNativeConfig(String config, Promise promise) { + UpdateModuleImpl.syncNativeConfig(updateContext, config, promise); + } + @ReactMethod public void setLocalHashInfo(String hash, String info) { UpdateModuleImpl.setLocalHashInfo(updateContext, hash, info); diff --git a/harmony/pushy/src/main/ets/PushyTurboModule.ts b/harmony/pushy/src/main/ets/PushyTurboModule.ts index ef076b13..66baf4b0 100644 --- a/harmony/pushy/src/main/ets/PushyTurboModule.ts +++ b/harmony/pushy/src/main/ets/PushyTurboModule.ts @@ -186,6 +186,17 @@ export class PushyTurboModule extends UITurboModule { this.context.setKv('uuid', uuid); } + // Provisioning for the native cold-start update check + // (NATIVE_CHECKUPDATE_DESIGN §10.1): the raw JSON persists as-is, parsed on + // read by the orchestrator; absent config = check disabled. Validated at + // write time — a corrupt config would otherwise silently disable the + // native check forever with no signal. + async syncNativeConfig(config: string): Promise { + logger.debug(TAG, ',call syncNativeConfig'); + JSON.parse(config); + this.context.setKv('nativeConfig', config); + } + async reloadUpdate(options: { hash: string }): Promise { logger.debug(TAG, ',call reloadUpdate'); const hash = this.requireHash(options.hash, 'reloadUpdate'); diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 9ed485e7..3ec70f08 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -37,6 +37,9 @@ // installed binary (packageVersion + embedded bundle size + mtime). Recomputed // only when the key changes, i.e. once per install. static NSString *const keyBundleHashCache = @"REACTNATIVECN_PUSHY_BUNDLEHASH_KEY"; +// Raw JSON persisted by JS (syncNativeConfig) for the native cold-start +// update check; parsed on read by the orchestrator. Absent = check disabled. +static NSString *const keyNativeConfig = @"REACTNATIVECN_PUSHY_NATIVE_CONFIG_KEY"; static NSString *const PushyErrorDomain = @"cn.reactnative.pushy"; // file def @@ -452,6 +455,30 @@ - (instancetype)init resolve(@true); } +RCT_EXPORT_METHOD(syncNativeConfig:(NSString *)config + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + // Provisioning for the native cold-start check (NATIVE_CHECKUPDATE_DESIGN + // §10.1). Validate at write time: a corrupt config would otherwise + // silently disable the native check forever with no signal. + if (PushyStringIsBlank(config)) { + PushyRejectError(reject, PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS)); + return; + } + NSData *data = [config dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + id object = data == nil ? nil : [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (![object isKindOfClass:[NSDictionary class]]) { + PushyRejectError(reject, PushyErrorWithCode( + pushy::error_codes::kInvalidOptions, + error != nil ? error.localizedDescription : ERROR_OPTIONS)); + return; + } + [PushyDefaults() setObject:config forKey:keyNativeConfig]; + resolve(@true); +} + RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash value:(NSString *)value resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) diff --git a/src/NativePushy.ts b/src/NativePushy.ts index 5e021dd8..0a47c3e1 100644 --- a/src/NativePushy.ts +++ b/src/NativePushy.ts @@ -16,6 +16,14 @@ export interface Spec extends TurboModule { setLocalHashInfo(hash: string, info: string): Promise; getLocalHashInfo(hash: string): Promise; setUuid(uuid: string): Promise; + /** + * Persist the config subset the native cold-start update check consumes + * (appKey, endpoints, afterDownload policy; NATIVE_CHECKUPDATE_DESIGN + * §10.1). Stored as a raw JSON string, parsed natively on read. JS is the + * single config source — a native side without persisted config silently + * skips its check, which doubles as the feature's rollout gate. + */ + syncNativeConfig(config: string): Promise; reloadUpdate(options: { hash: string }): Promise; restartApp(): Promise; setNeedUpdate(options: { hash: string }): Promise; diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index dfe5f715..58f6d0fe 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -34,6 +34,8 @@ const setupClientMocks = ({ supportedDiffVersion = 2, // 内嵌 bundle 的 sha256(core 层同步读预取值);'' 模拟未知 getBundleHash = mock(() => ''), + // undefined 模拟旧原生(无该方法,feature-detect 静默跳过) + syncNativeConfig = undefined, }: { isFirstTime?: boolean; markSuccess?: ReturnType; @@ -47,6 +49,7 @@ const setupClientMocks = ({ resetToPackagedBundle?: ReturnType | null; supportedDiffVersion?: number; getBundleHash?: ReturnType; + syncNativeConfig?: ReturnType; } = {}) => { (globalThis as any).__DEV__ = false; @@ -75,6 +78,7 @@ const setupClientMocks = ({ downloadAndInstallApk: mock(() => Promise.resolve()), restartApp, resetToPackagedBundle, + ...(syncNativeConfig ? { syncNativeConfig } : {}), }, buildTime: '2023-01-01', cInfo: { @@ -1357,3 +1361,55 @@ describe('client singleton', () => { expect(() => client.claimProviderMount()).not.toThrow(); }); }); + +describe('syncNativeConfig', () => { + test('persists the native check config on construction (silent strategy activates)', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-1'); + new Pushy({ appKey: 'demo-app', updateStrategy: 'silentAndLater' }); + + expect(syncNativeConfig).toHaveBeenCalled(); + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.appKey).toBe('demo-app'); + expect(config.afterDownload).toBe('setNeedUpdate'); + expect(config.endpoints.length).toBeGreaterThan(0); + expect(config.queryUrls.length).toBeGreaterThan(0); + expect(config.rnu).toBe('10.0.0'); + expect(config.rn).toBe('0.73.0'); + }); + + test('alert strategies keep activation with JS (afterDownload none)', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-2'); + new Pushy({ appKey: 'demo-app' }); + + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.afterDownload).toBe('none'); + }); + + test('setOptions re-syncs when the strategy changes', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-3'); + const client = new Pushy({ appKey: 'demo-app' }); + client.setOptions({ updateStrategy: 'silentAndNow' }); + + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.afterDownload).toBe('setNeedUpdate'); + }); + + test('an older native module without the method is skipped silently', async () => { + setupClientMocks(); + const { Pushy } = await importFreshClient('sync-config-4'); + // Must not throw even though PushyModule lacks syncNativeConfig. + new Pushy({ appKey: 'demo-app' }); + }); +}); diff --git a/src/client.ts b/src/client.ts index 5610a4de..7b17a5c3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -264,6 +264,48 @@ export class Pushy { log('onOptionsChange listener error:', e?.message || e); } } + this.syncNativeConfig(); + }; + + /** + * Persist the subset of options the native cold-start check needs + * (NATIVE_CHECKUPDATE_DESIGN §10.1). JS is the single config source; a + * native side without persisted config silently skips its check, so this + * is also the feature's rollout gate. Fire-and-forget: config sync must + * never affect the JS update flow. + */ + private syncNativeConfig = () => { + if ( + Platform.OS === 'web' || + typeof PushyModule.syncNativeConfig !== 'function' + ) { + // Older natives lack the method; on web PushyModule is a noop Proxy + // and the feature-detect would false-positive. + return; + } + const { appKey, server, updateStrategy } = this.options; + if (!appKey || !server?.main?.length) { + return; + } + const config = { + appKey, + endpoints: server.main, + queryUrls: server.queryUrls ?? [], + // The native check may activate a downloaded version (next launch) + // only under the silent strategies; alert-style strategies keep + // activation with the JS side (§6/§10.1). + afterDownload: + updateStrategy === 'silentAndNow' || updateStrategy === 'silentAndLater' + ? 'setNeedUpdate' + : 'none', + rnu: cInfo.rnu, + rn: cInfo.rn, + }; + Promise.resolve(PushyModule.syncNativeConfig(JSON.stringify(config))).catch( + (e: any) => { + log('syncNativeConfig failed:', e?.message || e); + } + ); }; private providerMounted = false; From 95c73c6d4ef5cb14f4aaf8ff32663fa5a36a5acc Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:33:11 +0800 Subject: [PATCH 10/29] feat(ios): native cold-start update check orchestrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RCTPushyOrchestrator implements NATIVE_CHECKUPDATE_DESIGN §10 on iOS: once per process, five seconds after +bundleURL (the integration-guaranteed anchor), a utility-QoS thread reads the JS-provisioned config (absent = check disabled), builds the request through cpp/update_flow_core, walks the ordered endpoints sequentially with per-request timeouts (queryUrls discovery merges remote candidates after a failed round — no hedged race on this latency-insensitive path), and hands the raw response to HandleCheckResponse. A download decision drives the existing pipeline through a bare bridge-less module instance (performUpdate; progress events are hasListeners-gated), trying each attempt's candidate URLs in order: diff → pdiff → full. On success the version info persists like setLocalHashInfo, and under the silent strategies (afterDownload: setNeedUpdate) the version activates for the next launch — which is the whole point: a device bricked by a bad hot update pulls the fixed version before any JS runs. The raw response is cached with a timestamp for the JS-side reuse step (§10.3). Failures are silent and bounded: one round per launch, no retry storms, no version blacklisting. getBundleHash's compute is extracted into PushyBundleHashSync, shared with the orchestrator's request body. cpp/update_flow_core joins the podspec. Verified with clang -fsyntax-only against the e2e Pods in both DEBUG=0 and DEBUG=1 shapes. Co-Authored-By: Claude Fable 5 --- ios/RCTPushy/RCTPushy.mm | 396 +++++++++++++++++++++++++++++++++--- react-native-update.podspec | 2 + 2 files changed, 367 insertions(+), 31 deletions(-) diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 3ec70f08..4134dfb0 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -7,6 +7,10 @@ #include "../../cpp/patch_core/error_codes.h" #include "../../cpp/patch_core/patch_core.h" #include "../../cpp/patch_core/state_core.h" +#include "../../cpp/update_flow_core/flow_json.h" +#include "../../cpp/update_flow_core/update_flow_core.h" + +#import #if __has_include("RCTReloadCommand.h") #import "RCTReloadCommand.h" @@ -287,8 +291,54 @@ + (void)excludeFromBackup:(NSString *)path; - (void)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination completionHandler:(void (^)(NSError *error))completionHandler; ++ (NSString *)downloadDir; ++ (NSURL *)binaryBundleURL; ++ (NSString *)packageVersion; ++ (NSString *)buildTime; +@end + +// Native cold-start update check (NATIVE_CHECKUPDATE_DESIGN §10): runs once +// per process, a few seconds after launch, entirely independent of the app +// bundle — this is what lets a bricked hot update be replaced on the next +// launch. Decisions come from cpp/update_flow_core; this class is IO glue. +@interface RCTPushyOrchestrator : NSObject ++ (void)scheduleFromColdStart; @end +// Shared by the getBundleHash RCT method and the native cold-start check. +// Returns @"" when unknown; blocking (sha256 of the embedded bundle on first +// call per install, cached afterwards) — call off the main thread. +static NSString *PushyBundleHashSync(void) { + NSString *path = [[RCTPushy binaryBundleURL] path]; + if (path == nil) { + return @""; + } + NSDictionary *attributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; + if (attributes == nil) { + return @""; + } + NSString *cacheKey = [NSString stringWithFormat:@"%@|%llu|%.0f", + [RCTPushy packageVersion], + attributes.fileSize, + [attributes.fileModificationDate timeIntervalSince1970]]; + + NSUserDefaults *defaults = PushyDefaults(); + NSString *cached = [defaults stringForKey:keyBundleHashCache]; + NSString *cachedPrefix = [cacheKey stringByAppendingString:@"|"]; + if ([cached hasPrefix:cachedPrefix]) { + return [cached substringFromIndex:cachedPrefix.length]; + } + + NSString *hash = PushyFromStdString( + pushy::digest::Sha256File(PushyToStdString(path))) ?: @""; + if (hash.length > 0) { + [defaults setObject:[cachedPrefix stringByAppendingString:hash] + forKey:keyBundleHashCache]; + } + return hash; +} + @implementation RCTPushy { dispatch_queue_t _fileQueue; bool hasListeners; @@ -298,6 +348,11 @@ @implementation RCTPushy { + (NSURL *)bundleURL { + // Integration guarantees bundleURL runs at every startup, which makes it + // the natural anchor for the once-per-process native check. Cheap: + // dispatch_once + a delayed dispatch, outside the state lock. + [RCTPushyOrchestrator scheduleFromColdStart]; + __block NSURL *resolvedURL = nil; PushyWithStateLock(^{ NSUserDefaults *defaults = PushyDefaults(); @@ -592,37 +647,7 @@ - (instancetype)init resolve(@""); #else dispatch_async(_fileQueue, ^{ - NSString *path = [[RCTPushy binaryBundleURL] path]; - if (path == nil) { - resolve(@""); - return; - } - NSDictionary *attributes = - [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; - if (attributes == nil) { - resolve(@""); - return; - } - NSString *cacheKey = [NSString stringWithFormat:@"%@|%llu|%.0f", - [RCTPushy packageVersion], - attributes.fileSize, - [attributes.fileModificationDate timeIntervalSince1970]]; - - NSUserDefaults *defaults = PushyDefaults(); - NSString *cached = [defaults stringForKey:keyBundleHashCache]; - NSString *cachedPrefix = [cacheKey stringByAppendingString:@"|"]; - if ([cached hasPrefix:cachedPrefix]) { - resolve([cached substringFromIndex:cachedPrefix.length]); - return; - } - - NSString *hash = PushyFromStdString( - pushy::digest::Sha256File(PushyToStdString(path))) ?: @""; - if (hash.length > 0) { - [defaults setObject:[cachedPrefix stringByAppendingString:hash] - forKey:keyBundleHashCache]; - } - resolve(hash); + resolve(PushyBundleHashSync()); }); #endif } @@ -1137,3 +1162,312 @@ + (NSString *)buildTime #endif @end + +#pragma mark - native cold-start check orchestration + +// Raw response cache for the JS side to reuse instead of re-checking +// (NATIVE_CHECKUPDATE_DESIGN §10.3): {"ts": , "body": }. +static NSString *const keyNativeCheckCache = @"REACTNATIVECN_PUSHY_NATIVE_CHECK_RESP_KEY"; + +// Blocking JSON HTTP round-trip on the orchestrator's utility thread. Returns +// the response body on 2xx, nil on any failure. The semaphore timeout is a +// backstop over the request's own timeoutInterval. +static NSString *PushyHttpRequest(NSString *urlString, NSString *method, + NSString *body, NSTimeInterval timeout) { + NSURL *url = [NSURL URLWithString:urlString]; + if (url == nil) { + return nil; + } + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = method; + request.timeoutInterval = timeout; + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + if (body != nil) { + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding]; + } + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block NSString *result = nil; + [[[NSURLSession sharedSession] dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSInteger status = [(NSHTTPURLResponse *)response statusCode]; + if (error == nil && status >= 200 && status < 300 && data != nil) { + result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } + dispatch_semaphore_signal(sem); + }] resume]; + dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, + (int64_t)((timeout + 5) * NSEC_PER_SEC))); + return result; +} + +@implementation RCTPushyOrchestrator + ++ (void)scheduleFromColdStart { +#if !DEBUG + // Once per process; a few seconds of delay keeps the check away from the + // cold-start critical path (§7 R5) — its result targets the NEXT launch. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), + dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + @try { + [self runOnce]; + } @catch (NSException *exception) { + // The rescue path must never take the app down with it. + RCTLogWarn(@"RCTPushy -- native check crashed: %@", exception.reason); + } + }); + }); +#endif +} + +// A bare module instance drives the existing download/patch pipeline without +// a bridge: the file queue is process-global and progress events are gated on +// hasListeners (never set without a bridge). ++ (RCTPushy *)engine { + static RCTPushy *engine; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + engine = [[RCTPushy alloc] init]; + }); + return engine; +} + ++ (void)runOnce { + NSUserDefaults *defaults = PushyDefaults(); + NSString *configJson = [defaults stringForKey:keyNativeConfig]; + if (configJson.length == 0) { + // No persisted config (old integration / first ever launch): the + // native check silently does not run — this is the rollout gate. + return; + } + bool ok = false; + flowjson::Value config = flowjson::Parse(PushyToStdString(configJson), &ok); + if (!ok || !config.IsObject() || config.Get("disabled").Truthy()) { + return; + } + NSString *appKey = PushyFromStdString(config.Get("appKey").AsString()); + if (appKey.length == 0) { + return; + } + + __block NSString *currentVersion = nil; + __block NSString *rolledBackVersion = nil; + PushyWithStateLock(^{ + pushy::state::State state = PushyStateFromDefaults(PushyDefaults()); + currentVersion = PushyFromStdString(state.current_version); + rolledBackVersion = PushyFromStdString(state.rolled_back_version); + }); + NSString *uuid = [defaults stringForKey:keyUuid] ?: @""; + + flowjson::Value identity = flowjson::Value::Object(); + identity.Set("packageVersion", + flowjson::Value::String(PushyToStdString([RCTPushy packageVersion]))); + if (currentVersion != nil) { + identity.Set("currentVersion", + flowjson::Value::String(PushyToStdString(currentVersion))); + } + identity.Set("uuid", flowjson::Value::String(PushyToStdString(uuid))); + if (rolledBackVersion != nil) { + identity.Set("rolledBackVersion", + flowjson::Value::String(PushyToStdString(rolledBackVersion))); + } + + flowjson::Value cInfo = flowjson::Value::Object(); + cInfo.Set("rnu", config.Get("rnu")); + cInfo.Set("rn", config.Get("rn")); + cInfo.Set("os", flowjson::Value::String(PushyToStdString([NSString + stringWithFormat:@"ios %@", [[UIDevice currentDevice] systemVersion]]))); + cInfo.Set("uuid", flowjson::Value::String(PushyToStdString(uuid))); + + flowjson::Value input = flowjson::Value::Object(); + input.Set("packageVersion", identity.Get("packageVersion")); + if (currentVersion != nil) { + input.Set("currentVersion", identity.Get("currentVersion")); + } + input.Set("buildTime", + flowjson::Value::String(PushyToStdString([RCTPushy buildTime]))); + input.Set("cInfo", cInfo); + input.Set("supportedDiffVersion", + flowjson::Value::Number(pushy::hbc::kSupportedDiffVersion)); + input.Set("bundleHash", + flowjson::Value::String(PushyToStdString(PushyBundleHashSync()))); + + std::string bodyJson = + flowjson::Stringify(updateflow::BuildCheckRequestBody(input)); + NSString *body = [NSString stringWithUTF8String:bodyJson.c_str()]; + + NSString *responseText = [self runCheckRequest:config appKey:appKey body:body]; + if (responseText == nil) { + RCTLogInfo(@"RCTPushy -- native check: no endpoint reachable, giving up until next launch"); + return; + } + + // Persist the raw response for the JS side to reuse (§10.3), regardless + // of what the decision turns out to be. + NSDictionary *cacheEntry = @{ + @"ts": @((long long)[[NSDate date] timeIntervalSince1970]), + @"body": responseText, + }; + NSData *cacheData = [NSJSONSerialization dataWithJSONObject:cacheEntry options:0 error:nil]; + if (cacheData != nil) { + [defaults setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding] + forKey:keyNativeCheckCache]; + } + + flowjson::Value decision = updateflow::HandleCheckResponse( + PushyToStdString(responseText), identity, false); + if (decision.Get("action").AsString() != "download") { + RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", + decision.Get("reason").AsString().c_str()); + return; + } + NSString *hash = PushyFromStdString(decision.Get("hash").AsString()); + if (!PushyIsSafePathComponent(hash)) { + return; + } + + NSString *bundlePath = [[[RCTPushy downloadDir] + stringByAppendingPathComponent:hash] + stringByAppendingPathComponent:BUNDLE_FILE_NAME]; + BOOL downloaded = [[NSFileManager defaultManager] fileExistsAtPath:bundlePath]; + if (!downloaded) { + downloaded = [self performAttempts:decision.Get("attempts") + hash:hash + originHash:currentVersion]; + } + if (!downloaded) { + return; + } + + // Persist name/description/metaInfo alongside the version, mirroring the + // JS side's setLocalHashInfo after a successful download. + const flowjson::Value &info = decision.Get("info"); + flowjson::Value hashInfo = flowjson::Value::Object(); + for (const char *key : {"name", "description", "metaInfo"}) { + if (info.Get(key).IsString()) { + hashInfo.Set(key, info.Get(key)); + } + } + [defaults setObject:[NSString stringWithUTF8String:flowjson::Stringify(hashInfo).c_str()] + forKey:PushyHashInfoKey(hash)]; + + if (config.Get("afterDownload").AsString() == "setNeedUpdate") { + // Silent strategies only: activate for the next launch. Alert-style + // strategies leave activation to the JS side (§6/§10.1). + NSError *error = nil; + if ([[self engine] switchVersion:hash error:&error]) { + RCTLogInfo(@"RCTPushy -- native check: downloaded %@ and set for next launch", hash); + } else { + RCTLogWarn(@"RCTPushy -- native check: switchVersion failed: %@", + error.localizedDescription); + } + } else { + RCTLogInfo(@"RCTPushy -- native check: downloaded %@, activation left to JS", hash); + } +} + +// Sequential fallback over the ordered candidates (§5.1): one request at a +// time with its own timeout; after the configured round fails, queryUrls +// discovery merges remote candidates (excluding the already-tried) for one +// more round. No hedged race on purpose — this path is latency-insensitive. ++ (NSString *)runCheckRequest:(const flowjson::Value &)config + appKey:(NSString *)appKey + body:(NSString *)body { + double sample = arc4random() / 4294967296.0; + flowjson::Value ordered = + updateflow::OrderEndpointCandidates(config.Get("endpoints"), sample); + NSMutableSet *tried = [NSMutableSet set]; + for (const auto &endpoint : ordered.elements()) { + NSString *base = PushyFromStdString(endpoint.AsString()); + if (base == nil) { + continue; + } + [tried addObject:base]; + NSString *response = PushyHttpRequest( + [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], + @"POST", body, 10); + if (response != nil) { + return response; + } + } + for (const auto &queryUrl : config.Get("queryUrls").elements()) { + NSString *listUrl = PushyFromStdString(queryUrl.AsString()); + if (listUrl == nil) { + continue; + } + NSString *listText = PushyHttpRequest(listUrl, @"GET", nil, 10); + if (listText == nil) { + continue; + } + bool ok = false; + flowjson::Value remote = flowjson::Parse(PushyToStdString(listText), &ok); + if (!ok || !remote.IsArray()) { + continue; + } + for (const auto &endpoint : remote.elements()) { + NSString *base = PushyFromStdString(endpoint.AsString()); + if (base == nil || [tried containsObject:base]) { + continue; + } + [tried addObject:base]; + NSString *response = PushyHttpRequest( + [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], + @"POST", body, 10); + if (response != nil) { + return response; + } + } + break; // one successfully fetched remote list is enough + } + return nil; +} + ++ (BOOL)performAttempts:(const flowjson::Value &)attempts + hash:(NSString *)hash + originHash:(NSString *)originHash { + RCTPushy *engine = [self engine]; + for (const auto &attempt : attempts.elements()) { + const std::string &type = attempt.Get("type").AsString(); + PushyType pushyType = type == "diff" ? PushyTypePatchFromPpk + : type == "pdiff" ? PushyTypePatchFromPackage + : PushyTypeFullDownload; + if (pushyType == PushyTypePatchFromPpk && originHash.length == 0) { + continue; // diff patches from the running version; none running + } + for (const auto &urlValue : attempt.Get("urls").elements()) { + NSString *url = PushyFromStdString(urlValue.AsString()); + if (url == nil) { + continue; + } + NSMutableDictionary *options = + [@{@"updateUrl": url, @"hash": hash} mutableCopy]; + if (pushyType == PushyTypePatchFromPpk) { + options[@"originHash"] = originHash; + } + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block NSError *resultError = nil; + [engine performUpdate:pushyType options:options callback:^(NSError *error) { + resultError = error; + dispatch_semaphore_signal(sem); + }]; + // Backstop only — the downloader and patch pipeline carry their + // own timeouts/failures. + if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(600 * NSEC_PER_SEC))) != 0) { + RCTLogWarn(@"RCTPushy -- native check: %s attempt timed out", type.c_str()); + return NO; + } + if (resultError == nil) { + return YES; + } + RCTLogInfo(@"RCTPushy -- native check: %s attempt failed: %@", + type.c_str(), resultError.localizedDescription); + } + } + return NO; +} + +@end diff --git a/react-native-update.podspec b/react-native-update.podspec index 46b704a4..9432c0de 100644 --- a/react-native-update.podspec +++ b/react-native-update.podspec @@ -110,6 +110,8 @@ Pod::Spec.new do |s| s.subspec 'RCTPushy' do |ss| ss.source_files = ['ios/RCTPushy/*.{h,m,mm}', + 'cpp/update_flow_core/flow_json.{h,cpp}', + 'cpp/update_flow_core/update_flow_core.{h,cpp}', 'cpp/patch_core/archive_patch_core.{h,cpp}', 'cpp/patch_core/digest.{h,cpp}', 'cpp/patch_core/hbc_transform.{h,cpp}', From 03450c664c785baa41b98bda0e9aa9af33dbccd2 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:37:15 +0800 Subject: [PATCH 11/29] feat(android): native cold-start update check orchestrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NativeCheckOrchestrator mirrors the iOS orchestrator on Android (NATIVE_CHECKUPDATE_DESIGN §10): once per process, five seconds after getBundleUrl on a low-priority daemon thread, gated on the JS-provisioned config (absent = disabled) and on BuildConfig.DEBUG. Decisions come from cpp/update_flow_core through the new NativeUpdateFlow JNI surface (string-in/string-out JSON: buildCheckRequestBody, orderEndpointCandidates, handleCheckResponse), compiled into librnupdate.so — all four prebuilt ABIs rebuilt and re-verified (exported symbols including the three new ones, 16KB page alignment). IO reuses what exists: OkHttp for the sequential endpoint fallback with queryUrls discovery, DownloadTask via UpdateContext.downloadPatchFromPpk/downloadPatchFromApk/downloadFullUpdate for diff → pdiff → full attempts, switchVersion for next-launch activation under the silent strategies, and the raw response cached in SharedPreferences for JS reuse (§10.3). computeBundleHash becomes package-private to feed the request body. Verified: javac of main+oldarch against the RN 0.85 AAR + okhttp classpath. Co-Authored-By: Claude Fable 5 --- android/jni/Android.mk | 6 +- android/lib/arm64-v8a/librnupdate.so | Bin 273048 -> 289808 bytes android/lib/armeabi-v7a/librnupdate.so | Bin 170360 -> 182900 bytes android/lib/x86/librnupdate.so | Bin 247844 -> 267000 bytes android/lib/x86_64/librnupdate.so | Bin 273624 -> 290856 bytes .../update/NativeCheckOrchestrator.java | 352 ++++++++++++++++++ .../modules/update/NativeUpdateFlow.java | 22 ++ .../modules/update/UpdateContext.java | 9 +- cpp/update_flow_core/update_flow_jni.cpp | 59 +++ scripts/verify-android-so.js | 3 + 10 files changed, 449 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java create mode 100644 android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java create mode 100644 cpp/update_flow_core/update_flow_jni.cpp diff --git a/android/jni/Android.mk b/android/jni/Android.mk index a80698ee..830d356e 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -10,7 +10,8 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/HDiffPatch \ $(LOCAL_PATH)/HDiffPatch/libHDiffPatch/HPatch \ $(LOCAL_PATH)/lzma/C \ - $(LOCAL_PATH)/../../cpp/patch_core + $(LOCAL_PATH)/../../cpp/patch_core \ + $(LOCAL_PATH)/../../cpp/update_flow_core Hdp_Files := \ hpatch.c \ @@ -28,6 +29,9 @@ LOCAL_SRC_FILES := \ ../../cpp/patch_core/patch_core_android.cpp \ ../../cpp/patch_core/state_core.cpp \ ../../cpp/patch_core/update_core_android.cpp \ + ../../cpp/update_flow_core/flow_json.cpp \ + ../../cpp/update_flow_core/update_flow_core.cpp \ + ../../cpp/update_flow_core/update_flow_jni.cpp \ $(Hdp_Files) include $(BUILD_SHARED_LIBRARY) diff --git a/android/lib/arm64-v8a/librnupdate.so b/android/lib/arm64-v8a/librnupdate.so index 4c7586931e9d1e0a9345727ec400224f9a18a3ba..d830350e88727d5040fec32a9f3cb7fb7ed01745 100755 GIT binary patch delta 90131 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

nPS~xA9eCj9+7IhSZvc^?=<{ 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 delta 76250 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 delta 50101 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 diff --git a/android/lib/x86/librnupdate.so b/android/lib/x86/librnupdate.so index 457d312a16fe0439310920d65003656919d76b78..c7a63b9b2311acbb52d390cf04e5e9c2a9fd30a3 100755 GIT binary patch delta 86770 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 delta 69720 zcmc${4OCRs{{Mf@nGt4SluEQkHl-+b9j6u>^rVKLa2n zc`P8eKX8LR^#=Q+?1JR7eaZ_!KRnb|H`u?q!T$XX_OmzG&);DG^9K8s>+Lo{?QSL| z3p@h>H`qtsV87)C``l~n{SiL?8at0~#SQjHuCp%^geR|42*L|D*x3#C*Ke@Dd4s){ z^;ZvHi0;z+!?W?z4ff6(?3Zt_t9bvQA0FP9?NtxA-ExC=-VJuk4fYi`*wb#X+itLb zgOsL+ib67SGeGbY%;=%h!bRb8=wZK0SOLZUC{gI^RtRsyUI)7h+u&l<@CuHl;G* zLzzU$r{Mi-+-V;)0aOYtayC+{VBqVryl>ib-A!&`Qwj2`na&- z!L&!8sJz2>u2kvhcUrWiq>X*zQ6cT`5B!7p_#>;*Q|`Ae=T^RE@S?X6TZwJNc47yy zlh{SqL^Cmr7*32NMiHZlF~nG695J4lKuolekVH%- zrVvw!X~cA*m6$=yBxVuq#C&1_v5;6qv=x(3LM$bg5zC2H#A;#m#7JTkF`5`d zj3veqbH8{M=C&7^20I*A>`E}~FFeSxSU>WF%xfoLQK5=}%iF-#%GUpOfuh*897Vhk~s z7)OjJT8Ih6L}C&#nV3RMC8l%o@oyzX1~HSEMYI!hi21|xlKl24W+ziP%hRCAJZr#CBo_v6I+UL*u`j6v9DjW1@zrBkGApVj$5( zG!w&!;lv1HBr%E@eUQd~3@KuXaYPF-ftW~4A|?}4h^fRhVmi@E%phhGvuq^f5c7!z z#6n^bv6xswEG3o^%ZU}lDq=OUmRLu$)sxUbY$P@jn~5#NR$?2`No*%}5W9%oL;=Tm zzSgLT8aHi%jud*LfoLQK5=}%iF^m{Nj3h=8qlq!ZSYjM8ULnSxg%kCr#0+94F^gy?<`DCV1)O~R7m}igSVAl%mJ!Q|6~roHHL-?ROROW-6B~$)#AZ+# z|1G3wCAJZr#CBo_v6I+E>?R6_sf~#mqMm3V8i|32Y5bc=VJ3zV!-)~ZNMaN*nixZj zBgPXg!~|j@F^QOb*yhf03Mo>FX~cA*m6$=yBHD>L#C&1_v5;6qEGCu^OKl{S5zC1c z#A;# zEFcyUi-{$~QeqjgoLE7u;^gDMniMs}T4Eisp4doiA~q9Sh^@pnqLbK8>>zd$yFg|9 zcauUm;%;LNQAgAh4MZa`kZ2;BiDATWVgxag7)6XZLgPP{6mi6OqJ@}1Oe7`|lZh$B zRAL%2otQz)BxVuqM{MpA=a3?wSU@Z!77>eyCB#x<8L@&`MXV;)5NnBb#CjVE4a7!b z6S0}tLTn{EiS5J=VkfbS*i97Pr{PP~5N$dV^h5*ENHh`6#4utwF@hLLj3P!8V~DZD zIAXk;Ho-!QL}C&#nV3RMC8iP6iB@6;F_V}@v=eiP1;j#y7=J~iC?=K=ONnK~a$*Is zidapoA=VP>i4DX?ViU2MlaK!vQnV7=h)!ZVv4hx2>>>*I7^6(hL=90#)DsOvBQX$E z#=nUaW?~pIoESljB1RKqh_S>tVm#48Oduu_lZeSjY5b>VhOSAD2@MeQdAJDh}FazVlA8_`K@w~^36>?C#( zyNN;_wJ}jc)DiVWBQcO@BASU|#Bic5f`mw76fv3@LyRTH5iP_7Vj?k#m`qF|rV`VL z>2BHtD=9LFnZzt&4l$otKrAE{5sQf>#8P4zv7A^ztWt>aS51mqVjZ!b*g$L~HW8bN zEyPx08_`K@Cw36Kh~1og{0kpY+Y&WI9Z^p-5RJq@qKRlGh7lu(k;EusG%*HL#(yj+ z;)wA?3o(J1NK7Io6H|$4#B`#Sm_f`WW)bZl(D=_GMLw~BSV$})mJmycWyEq~1+j`) zO{^i-66=WdAJF)3AVnjwnb<;XCAJZr#CBo_v6I+E>?R7wsELUhqTWV=foLQK5=}%i zF^m{aj37o5qlnSO7-AeTo@gQ35=clSCJ~c~Da2G_8Zn({C1wz_h<0KQF`rmKEOgT* z6p^BsSVAl%mJ!Q|6~roHHL-?RN316{5F3e2#AbyUe=VeFCAJZr#13L7v5VME6pmBd z5;a5}QBO1wjl@7sKK@Ol2qT6QBZ!g2C}K1*h8RnXBgPXg!~|j@F`1YGD&s$u6luhC zqLr9I%p_(J?Zg~nKCysUL@Xwj5KD<=$7%eRlcIuHMXV;)5NnBb#Cl>Qv5DABY$3K1 z+lWqL`*9lo9i-?ab`iUYLcP0K8c47`OpIAUFBo+~ii6z8RVi~c5SVgRM(j2KRg zAVw0Sh|$CtPM&@&DdLFnL<=#Im_$q_rVvw!X~cA*m6$=yBxVuqpfdh*NKrs6Bo+~i zi6z8RVi~cVSV62JRugN8wZwX2!-q8f8%fbbY$mo4TZwH%C$XK_LF^=U5rvPaX^9%5 zj;Q~L#=n6SMq(h*L^Kn_h~dNtViYl&7(pghXNzF`1Y`OeLlht;7ss zCNYa>C*~0Ii3P+$qOFL8Vqyugj95;rAXX8pi8aJpVjZ!b*g$L~HW8cM#P@$vv=N=e zc47yylh{S%RpuPmy@D`SVgQR))MQ8 z^~45ZBe99tOl%>x65EJQV*AH5{yRv~MeHUD4b-+o4N*tb6AeToF_35?nu%e=2x4Rd zjsGZ8L=$6(vBWrHJkdf-ASM!%h{?oMVj3}>XeDOYNXR5+5$(hrVm`5eSV$})mJmyc zWyEq~1+j`)O|;dJP)n>M))O0ujl^bR3$c~hMsyO}i5^r7Vl**EA<~Z{MLf|$Oduu_lZeU06k;kdjhIff5;KTdL_0Bu zlaK#=QWOviiABU>VhOR7SVk--RuHR+HN;wC9kHI+04n3ZkrYkDW?~DmmDomf5<7^U z#4chtQNXipJ zB1JMWg_ufABc>Cr#0+94F^gy?<`DCV1;j#Pk&T36VhOR7SVk--+COzyX$~=;SU@Z! z77>eyCB#x<8PQfwLItskSWT=U))M(secq-`#Aad(v6a|HbQ0T%9mGyIZ9*3*x{1Pf z)MO1F~}FMqo@@p`zMlfQ9o1o6^W&54)28cw|K)p6nlvz`-=cMY6)&1~ev>)=36 zJjgY1;w7+|6K`k3IBj^%8_oqhgO1?DOWjCLJQa@O#ADlN&Okwk;lwN5Sk53!T%5!4 zD4)}WhYXy-f{?(8r_70*c=DXYiATN3oOrOC!igu*si4gp@0-)OB1{m{Iq?M8%89qs z8Ju_`oXLrI%~_my+id5=tKb|?yuHrn#CzxhPQ2MJFuRU(x**hZ#t1?K=L|HF z^263duVLcUb!`9Czu5k1$0{Au3ndml^PNi9%)KJ3j4iBuV0M&LSvJ=ovbwm(Sk$~f zDyQ83Pmz6eM<@FvzLizYcd_>uOxkqkMdod(V@;MrEPT#r_SwQOSnXX?m}=3*O$kkv ztqC8AtZMOHm3@~i5-U>^jpE;gpB}m5vZP;et$*~2>m~Vb_=q8sv<|*hZhF!IL z^#6$`;qMKKBOdaq|1O-ZH?bZBI1$q0nibaLx*Xc$>NCnMj|I&)xn=Ig&8c8F7m(fo zKh2}vx@}+t{ERX8xMqyF>emzYdfKj!c;T2H*Cqrk0V9x^Dn#5evd3kEJPq=0WK2Z~ z^@Z$!+ccOY$exIK<_eIpyWsCzumqX-4sl9|b0OsOFqd!baXmZ{#>W4F*&Rs=vU8oo z)<9Qjh-Hv|>koYCaz&O|q;WOO!80flF zdt3ux;#T&!z9(|oV>#Om@}|XU)|$+ z3%V~d(I1}Zas7~i8iV~#k0OoU;w_uLK)Whs5wYgk} zAbbK-1tacqxfa2gU>uWN7uf$OxGqKpEy? zm~}9_VQOKH!W@Tr8|F(Gu0H|O0&@W7pEWL<>!BK#tNJ~ci@|J#F&w(y9EEQ8HSQ(w z|0;}l5D|krz(-+%-baGR-DZECo7vzBm@=4IN8Nt7zfok;>d^kH;jj!I@?k!N`2prL zm_nFmK5)6>!I3bBU^c-%68_hM-r=YP@Iv`5gv{`ZW{6^_LJ_YB(qB-&woJ=zJK;xanWXxQf-bGa76q(XlO z?3^CM?N{LUQMf+`V})4`vj&Fip9ZsGR>HKPaOYrj(072}!CZ#n`afV?HvIE17&Scq z4JHh72uwf7Mi{OShnWa70_OkEz4$YiYXi&+Fbw948_bRFmE{@DxLCo_Z5g)Jo%n^# zK*;9VI7xw#paqNtQ@|9^4ytF0LMdnlYq>kv0v3YZpkbCMgy9$%3&w$UpcQPwFWi=Z zopVH?0ZfS#g)W_76U_5O!Hn}_As7!ff$9AGcn5y(7EA$a!A`IpG{=jA2ItC1Fc7qY zF<={*0v65}g#yk6qEG`ifvw#APElyWAt}~^guzl!=ZAxB8-xfPP|XVwAx=+KU;^w@ z?m~uO|HmRx$OO#^qEHT6K`Yz~!DiUozWT$RVNU@~u$L}IMqqCOo4LOw z$N*>nbvB%cV2xOM1)`@SP#a6Enp|u4Vv#k#&M$V1mnQAzllO4 zn2>~oxE(a(q#b!53IwXr-d3;)v=u|JCZo!c(?a}abuH}nhtO8A+aE`j!)|y25#Wg4 z1X?(AP$OW~3rGn5QZ^xju-iAIs=>$?Q8FCm+rTK$T7;$n?OV_^oG*()H5gee+JrU; zZN;LX$CVTcv8Cu_ zV8Ra60GI;maD`w6OOE+4oUqMR=&Z0ef%V+IS`<3L&I3pgcNJANNDyp$UlcOH!lQ@};ncP|QAmPd z1#`eQunLU)096UvL0t?Y2BSbjGpZU9G=YsgqLb(Yp!F1n9WF!c-=On?u`MVNs6LGX zflXjDSak*k!evRF6Bz()=5}NNg8Do%02W?AZv|t269p$&^}8q-0uga1h8b9QNfgpR z^A%Aj;&!kOOzA<>aJ>snGYkoWaiAT)wrvH?q9l}nks3*801aA6=o*IcukI%a<{(4_ zTEG;&BxHi-0g_M#CJdB>CQc)6tA-=Op^^{|mJXAIL@;)^B-p`7lO$AtRl$NF$_cK!ni zgKdu>r(j_^G76?VAqghjj3rnlAs&ofB?%dv8K`oue?}4-!NN>Q=mzV+KwO}utVY4W z&NXOauxYJi6UrczK8rTy3a}IzX?qTx1NM~X(Km48X2?O`0PDa^FeMj#11tm^z^WJE zpX>ACA2)N64)_OSH^4u?sRN5abAco@gDD#&L5<5$^Cp`lL_lcUBne4i6O)7-u(VJT zszCc@v>|AC5dp>^17HLgDN8~ESPEuYTERN732X-=x1eAskoqMwT{tofMuRCY zqiI03tr%?s!2p(lonRC9P$DURBcW~^di%{N$xifkuy8keJGYmkvw>}33)jDiUOpE3 zJ?Q12c`phCCcKRTf$B;W2&@BJL2H#H=*IE!|BfU?L9p(ZgcPu_S`rFC!vRUC0V8W9 z!3o+Aq685r*&#`Y0nLXojKCBy3#da=>zmF(0U9t zHXh^Oa2%ZriaM|ptg1)nx&@UuY80{u7$S2BFZ2 zHsKCGBcX}N02mLpfmvX}IZRAoN;~|4_FvG}x1maZm4q;`4YY9kc~mW!0v3RUUJc#$*3tX5L916c+hYO1IQ;v>-6jhC*mo}(*s%5feZnVU$z}JiH?Vo@{Uy_6 zcHjEZeNv`~!n;aOH2{&Yezmz!*27N<{AeKaAD)#K=#rt^$u{mA?9~lh$6OY)B|sbK1=l!H_<(ncfyKzn z3|$`{($4j8ERADc>{+iyh`QU^dNxAa1u@}vQCP3^qfeNh*6fY1RM5mK-R%gG^yomLP`&x*{pBSaeox%634$Yn?NjMS#Urm>@W*|q`$yYjNH56^G` zwC#4b;KdPM)e!5RV<%n=^=g9{_q-zNeUNFl7!A>q1MO(3iDQHh)_S23+QeLTmpt05 zj*BlS!8#zeyukLzp;~=&v;-(_Cl$x(7OQ395L~R4abO-+U6`vla{wVgg zm(oAsn6LqV`3i~hAFk8skR|BQ;>u4L{xv}tzk!`83Jr*d*a~Of{rC^>E6LEcZ{WSd zD+^-x1~zR=sJ0wpM}a6D=50J^(bbfBjLmSgZ*o_vx-Y7cxyP=7o!sI#ECQM|XgrDV z5V6o@K=-@Ss6$@z^WwJx@r7*cOQBx;Vj$~fUN_o$h-#c4e^%P^m6!aqozT>8#gSF% zY#$<|sUKRPgq=aic!eJiOt;h6B1C_ID;8mTq($3*zGD159G?(8dm-Z9WbH;c^m zN{BWMvgNcWEacTYC`#2E6VHAT9PO}yXE5` z+rJluJxWr;w)%PVdkD=B?iflSXF^`KHALG0Ir-;nE2aw?!#R=d-x}i0FC&`IyX%@? zNThXOr7Q{2@=J+U9ANkImNyn%-v!IyX#E|>WTm65F7eYgL6h+(4$#U{`bLSLH}8S* zmqd0Lk%dFHbc@1ZrN;lbKC(nO#`cJUtn{&)xA|%9&{Vs4Kjf?QJ=^@eDxk>}*dyCQ zy;>kPitMFrp;`?Vq74T_akNxr z;2M_xnz~QeKAmEi~c7*qZI3+IENn&e7i}z2hQ0{()yVxg%Hmpq$@ zQVdaOYHsG))D}aG7>f-ePiRo+)eg(^RnOy!kk|&tAofZrhAix55fXYCue@Qe`)UKR z7PmrEpk!t3)vWL-Hx7=m;tYw@Mg4JcTW< z`+0Rh({d}@^E&3&A;@baJN0^iHX33~q$G^H4_p;b@pA2|JXKuk1uELQ{0RBz&sm>qEq48j6uTpPku>i4UT% z0OIIAbqgfng5qa>nV+^44n+$kp|5)}Ec5efgeEhAtuG7p?uJ-!x1{V|8q3t$K+OLo zcT2)6N@e`=wr^iP|HMISSS(@w;S1xiHwJ4np-EpN319JKjK{BU{W3V3@5M2Wtt~Y6 zr|pCZ@if7q{$93X*KiG_w)-UEF(sW(-thA_Ul|;$k`Z5!mftzm!e4)$MWgGgKi(|3h!U#q=T*74YNVFmMnyd+37;rw zU)qK31T-xVvVOZmz4+Bs?Q%AGcYs$h#Kz_9-rWgus{aPM0Isszy!?-PGb|_ z4Du?5n4ZS&eRG6Y9mF!X*bcE(5w!-K2%NYYdREED**AT){Cdpt$hAX+UzBx1^RnV+ z>K?yg{K_o#QK@(D!An#IT?%x{SYfO8U`mF@m@Ww`l+O6Z9zQR>*-ms{oqr9JXLD z&PNa}xi~lT3NikC{XlZUF*aWk<}2OqAmTEFpq3qyFhkLtMO-n^>#coyxv9=R(J?__jRl3NB2x7VkS$&b6sSNdw zhnOY1yECQ&Ap>@Gk&?3_i2S~7sZzv$RQYM^po!Ze36)ArTdFX(L({T_?WqdY2BIBe zUgk?6pCI17zU|}S*!D78^Ueq#o{AOL`eM}8AZ5)cgtZG7a;KC+{rw%x^U#!Uh5u0R zc8Ikd65IVwu(x3pdhG>?4c{_E8wok=f+TELGNaw+=ba2qIb7e$A2L1*a(k(y{K-@# zsO-QCp>slqIYjwEqY7ovsdrpm#C*LPp()VVj?gYAK+AC35SuoL@&ydohs zm$BdWjqu`EnfhHUV1KBV-)1&dNPRYtc5DRg6D6;W2{w%**=hX^L<~!`w>Jg(f z;i$uXxJKjU@SL6F_)LS@1*$N(cf!3^shca+zFrn+^7pfT2Lim(A(p$v0*DR!*#c-Q zAhzshk3ejM*uI~whu8tJdq3NAAV_Pt8I4~p33n=So;`rs2%2^8_HkZT|> zdvAz$E#!8{Hpu+8Ht>+dw!RlUR5K2lJ|y-2X<(kAKO+h>i|T_?H+42SdE`A$NQwu{#ciXsaQY;`VbAd#uL5 z90z^9TcN4>9)&s>tksN%>km90G#5J!*9d6R+9Y-!VUr;1abr4)SMY%0d>%p~$fz9- z#-AiM>rmA21Gu&uIuuShBKEIvh))Y{M_%_eKy*DD>yvZ)3-5Lw%EQ53!slHvAyFSR3q9 z0{es9?qVa3jPPlLbp^M6%ND>IwvKXonEms}h`#Z#@?5F6;uQ_s`_5oidqmSW-J`X6 zwCzVUK84Uu#@P$?)N|wk-_##PVG)M2+P8*1{Qh#E9DF})6DZ;em>1$9 z_TA(DU(qk`J+ zJvOB-#3w8U_r_>$^+mR_F4QLxJIoV2^w;bqSTnHPT;{&_vyWhnW4P7kR+(LfHL6e& zj&ti%Z1e{+ecG|mB=WF3*^?iP@G-q839Gqv1AFa*X+DXtVjv6ZzpXfG+jUEv2U;ceZme1Du$seiBhAh#lq=V2R_8{Wot+V^qkhW5Ef~Pa>ba?o><=IAcAe|z$ljF! zdO5NmeX2KfAP*gLO=$k52*J2|**Lw(qZ+J;YV`Ak+usE`xyP9P^&)aub;DrhtJeD2( zI7;|(=B(pfL=Y)I@!&PI>(v;CN^Gl2bi$~U0MOQ`_I933w}B?c7F z)?ADocI1=6(nDVCwNEAv8OMFi=3QaZF>8O6#g(phMJ#sKvJ0R1v!Hg3ocxWb)%Ss~ z`S7LB?HE=nuO#za4 zQeOCqI8b^=z3I#0(o~5Be>rT>u4YN#{daM8TZgC9->+r~U*0w>Os!-*dm29a^DIxo zRFUV!3&Av^+_6*)oBD$aUX%{J*0p=rE}tjNjyam#Omr{!;{J2Ja_6WY{?sTQ$z7mv zEbqK;+258P<&kxv>icg))o)U<`%h0%Vb3IAu!#QBbQRmvI7%{LOY@aKYj4zQjM>MG z=l+%3!Pb5=Z1iW6k}0J-Y0!!Gp>w!fCi^&g7*}m~t61&V0R{)WL+i-?>9KAX>cZk| z6=hQ&$)H~j7f1GB3?rV`8j+QJHb|N-vFgtjsfT}s+ zzYY)27O&4h5&AwdfaawPSe)zJs@x8x4;iK?A#{1OHF+88Nf)hsvyVGj^XFrX-8ev@ z!96|Wg1cwzz90uDh(o0h1s3&1_?(yF$&vl*YIi<=MsMcLh1SY$*Lxc5DIU>m-BhIs z`afLDCj;kktmls9-KcM8%N5pf2Bj0&`(H#$qOhsyCu#l{N^ek!E{Ta>P96Lwyx-Un z-t3Xs-Cs^rS;6&R4px0Dx!YYe4vH_nT%bC0MPlZ!EUG;4t;R58@)ZdsGN5&)tkzox zy4xe>7lG~nYP2c{o(_B##&lngmUj;7ym*ylz8#ocMp65C054P+!MVePP)XBzcQ$f z{3Wp&%@IS7eu^*Ip3+@YaZkcMzj>&1+U2hEDSyc^N#fWczC8L>*EboTXJT33lQ#|g z_%e6Z=AzqQaye}V0ZpDRQj|_KCwz1JA5k!3eU-EP2OD1 zqz?r$Bgy1z?e+rF8k3IW%44)ixJyXcB>EYRfY%*dc+v2#%; zd--I5IEtBCg7hn~)S<)XJ&D{S>)*t^!8T5erSm3wQhDIo%_2^A})bRR$^jAE} zJZ?O9o)2#6uTD8@m3T8d{Ouy?=}x7-g6DPqTYWjHzHGnX73W#`em~Kl>Au&gw4Er( z_v3U+pX+w5)L8Cv`qxX9i@*0*seZvmIqgTI>Xi`{*^Qn(}gb@!4J>Bt_xnjQPs-O?T3vtd7tk%GQwxBnC_b$rJ%e%dC@|BelHhDjs8 ztDNn8PErkR5!i{J=MMVp8w~r)Sf1m~R{w>i3RQLJ8-a~E7w)$KPFPl>d1Gfox8mGL z)lxA3T&SuPd;fRNjaN3;%~PE}E3paf(Zedv;wp-t(N>PQH}`_`MJ$l^T5Bw? zyj|b2_3go`i>(5CyM3N26d`)rGgP~sQsvXXJR_=p`&wWZe_b%>_9v-eYkzVVHo~#C zE}i}D{8;JWyDabg2AUQ&^Kq&(KT2#)M}?GpKn{LS9HjDx zz5j*32e;t-hOV~Q`O6P@CG8o7p1CU*i}}kJW=k{LF%*@2gFd_ec>U3GAsN2|_q zr+j{?ID{qr)=%31gECN6&;KB?SALtWnhj#0OLOiRf{@xigdn zX(f8{^xuc5KE!DSo+p32??2B9Yj|jkZ?Ehq!D`^OzZJinlIN8taQVl7QP)+?Ks>>p z3}juuPgMPmt>O62`Ks+8W&mm34}8c;_x~h6Isth;)9E7>|HOXo95ZyrJO4S=PVD~p z43PYzdj>ez%6s%M_UX5M}gz6vgwCx)s`}B`s)p*Dl?o9LN)6$ofyxgkX zZ&8_l4)rPf8RGp)Zn&5w4LBp;86^%@ zt@&O_dBiuTdF|<@e$vpORwCN;k z{VO7%|0#D%&(ihW4q6WCe%V2cDf!aalV zy}7cx-tnctR9AwdZojHmwy=+Xi72qs#x53}n)ktd=#N~3I^2xR@^l>7FC<+VCw*M5 ztm~0BZ0ughPVcW|urJObkQ~dq?!$h-QOVUj@SJ8rzySG<%V{SC_SKaTRcJHjg)4W7 zfox&VF#P*y&p7E>A6C+nFSVK2Sl1o4K&KU#u#S9lF`6j%H|KaicWiebht{rWv!bwX zS78_G$lkU_nNBo$yp$*cd=&6-D8{ph2E}k>?;$_rZYTE#$i2#3!!?m+N4(kU&=e{W zIQ5%#!K14?CTtE7M@xT%ZC)rY9a!^>lCauQWiEFIcb@-f^HDK)&Q`RK zyE%Sw&v0r-joS|@G7^rwZn29+#ECp&=VfE4B_e_GsYFL!qRP;U>Z1u0$KJx!;Z`ew{yqNK)y2 zTKLIB!rc9H=#j?R3@nF;=opU3vF4QA8M@#6^r-yb{QA#O`z@&jVarZ2rwC{9w{|d1 z>9W=&3)W!A;sj3*x#av28S~rcT_##b=Go1|c!it`M5@OG5pnP&5s#3atP##WCkK0p z!-7oC7jUg|ZZOkk?B;f4bBfh$ zdSWs%&8M~8IU1DSu2d;9>;>a)mF!Z*U3kGACEV+S&U~UWS^H%#lu&7C)KMSher7vzNj2$+-<ge z?;tvpbtnZ5L{OUR@7N{@xkr$F-Vcs^uXd>W!)t^m-?9C#%ecD#zvUbq(403Inazpq zuS10fiXD@>tT!RkX7PeNMI#2cQ3$gCzJcx(Jl#;opRerf1o z8l~zns;#+~(5F}7LJ-#-*3t4sjTjs-U*qiL9b$sgvjh~Lw;+lTCP!+;akjCDFRx0; z*1A8DWT4gLK3jI=qC@E6ilW%v*+%k*KU3xzrAz&OP#HbSbOon2w^f-VVf~U@X-4`G zGL9aw#_%${3+wW22IOPPEW5c2Ts0`Si;ng0s|BaI!R2z^{Sp2V(1v`gw}f9TtZlD_ z-`ua!E(YH9_|4j`21%aiE8aBuj}m-ruC-1=)nQE(vuV<{V&98q`Kcb&)~_3_0`!|mVL#M<_malfC(_#kzkH@zQrd7Y?+lA0Cm8p4QE+^e8yKan(x6i4I0d# zI9Bqm3Jaf}P_zQ9Fj?=V^_K&5;>{6f{&{^aeBHU=%af8mYIwng-X-|v8u=-mm|)t8 zYZsJc+@Ic}Rh9P?O7ppUE3GWlTeiyQbmD{nKisjMJ%_BtIP&KG1L@$_j_2U^t@7l) zVvsmizNas4#RhMcSN9dolDJjg4!iA_S1~Af$3vxDfQ-Bc@?bwg&dxNOu->jkr0!b$ z$%ZttdGXyhgU-V|1ojTGypC(!t1aJmMDXZl9&^|6WbR2or08V^^vH$2h!5@$yo)cc z=d7N%tkjl5l2c7RLjr$h_MTj1Cg;h-MMPy4re=@_fR`}eoYyH{+@Ub?v56_tmwW$ z!vtZm^I$diuBX9($X97mJsM?!aq5fZ4R?tFBM;*lyprhL)5>-nJ4Jqbh&W%`uLM6g zPMN9YSU=GqJ}=+nCyw!tL!jO~XXF~N<&b4R@s@yHcs+y|e0c8)#$~{{;pp{7ECSW? z4}K`gl$T|VUJMK#gZ_o$svVhdMMoWWRH<@w45b^Uw9B*g;@GetWPwN3hexGy%oS0y ze69Tj^SpEX%Suo7aaO-1zpNKc{_lHkRb$P{Rm*c&{ZAmZc{}$h4W`^Uy$rdxMlx3U zBwkrNWl5g!VjMFAzD)%W$55q4_x2&sk^P7J z431Owo$gT;;mCe~j~{;Vj8%wtuXqYR6T!1<{izz#cs20fT1I@4T*vJTu^`!NaaYBA z8|kK66s6UNd)WI9rqJDxP|e9$iNc(};(U%R0Z%CTl(OIs;I)d1PT&D5st`cW18@bO z(hvS6hYY}6K3MVXw=UN`K>6CPY*=kIa`FIip4cSs8X(S)c2vo~3=l*5jVn@WKZPgs zMUias7YF$atyNml>0czr_=^F9ZbM?@|D1Cc4{`Z`QKB1JC8zs~!=y!*6+Bp+EmYrt*h{1;`ff#USP z@9=z&$V8qIrW;ENhMaP*jA@7WXZ%8d5?wH&qwZu^)?HkKp+ofcsmC^7*QfYC%+sHO zk4W;Kfntc%vswOfpcpLqz9@GM6o*aWov611nGl`wRbd}u;=K-E2tD0~=WPgn1D{X% zTs~SUTLy`Pq}`Qr>L78(9g#{;@mu!-Qq7xf@bsWz?q2m2f}q<(o7Q`P~C$^c8#w!<=@^i*}j@ z&rb7Py7N0GKR#HD9`LbyOz>_Q$0K3y$+d&g0{h;QPY)JHg`LC4HD%^k&ZDJiG+Xd( zC)(}i_uTy~gvn!#;!s=MTf9?y(tpVl7F}m7TzLGUAo6rJ6&pNf?xt(nB)dH#_k^cO zCLco9S6`iu!`8X1k2$i3xz~G5Zi2GqRhr`#EC%QkJo1=%ND-CGw;+06$tV>+@i@=n zy(jlWvsyK-mMDn4N2y39ro26}W{7AuJVVY`C#0hr<(MJjFx8_6B{_MBILfaGImgm9 z4GnH|zPC{>8X_i3CpXHMhls(8XzsyXAI&{sJP#H4YRS9JkLUw5+wd{vkH4#c3JX{I z1ndX!w-zwd+y`BlNB8<$@*@G_pkYbB!!Iw=C5*QS=WevMrxW~EE^iCK{}y~28?)St zXfb6!^eJp-A3#;RXGf)KvhSj827)S=g)8~$M1u-3Bj{OF3SZfs<0#Kq^63ct;o9CM zI{MrkUapVvRtT9j{wau7U-{OdqW?XALA9=y=hV;s8t?LOcBj3Dx*IR~k9=9f>VA3y zBH+oL8-wb^j>B}ruC9hWR&9{C4i$~ksonB{q2iLE-yxu<5UX&o^!wz5?JGL zgXDNuo)L(TMA}AqaiBO#8nIVi8z?RkXUSg%V$byK+w!GAe6@?-D-Rwf4!6xhOYnRx z+J&gF*GH~R$hEO|F){Qus2*n{BTh=W$E~;VAnhlVMjL}5?!gURez~IK$2g@ga(@}n z;d%!P^C4thnO*1M$v|$`*)MwfU_i`s@Qb;AS8xZ)H$`ZHrw$5o3ocfqFRJCs!^BuI zRGuCrh7M`QB?G)Do70KPM5JsB4R6S6g2W*Szj*4IcZG1C^iC`Ryw78XI%js*^OdXn zR>h0`suzqs-nR98D}q2ge3^VMNK70yCQ`WsT7Y;BxfgQZcb@F+8?I;MvJfmz0%)Yn9&VvqxS#M!Z?>Ss{AM(I(MXvhS1UnZ$|GjGgjYlNdDY5B@ofFWSh+ zTlhmP*X%UrydWPMiQ4`B9r=_A6PU6Er!DrQFDPStT<$S61U5SwoB;$J_i=KGMArDu zmB*L^Z|;5cX1=4P+q#okz2ii=t&^}k@y>o3FZ!-OnfPJG=kb_!2*N|X8=^43b^J|3 z?|Di240=MzQO0=QD-i^F(NKm;-FY3$BkDpD?hGBlbbao*8*#h_g2S~>16=b>`BZiF~f`k+$Qj1)tq_n(&|MvC*Ki#hT$y#7z-$XiBY zTN?YkyFD(vEw_vmhxNPm$n)^?vbR|rZCm#Ib=h9Qv;8o1d~&Tt6Jei9bGa_{XQ3@849u`~IUsP7W30q>&Z!u26BIG~rqKN~ma+He}0B+=PAS z;J4&`H;Lobx8g4kDPJmsKi-4f%*WT0ym{)}W1~*s6P)TP^{6AL&V~4%C}z7qTHd?I z{mJ6k+wM;mBllc)2R~Z+@=dubjCYRhvS}1b`~F(xv#+%3O*wItI8J)QCg+Y4CrEwY zlxs)fF+k>C`KwW4g!DnV@-b-8nsVfWe~H1+9E6tyyin&W_-CL|()4m!9WI8d58=bU z+?p<$=KQ`JzLnPq>s%QDyc^G1gO0+VAc$SK?(*y$dFL2}S)=zrg0rJ%oATCf*)dw2 zE1kR|e>GYhFEwY$+A(5Szdc!q8<$?F{$pxCdD}>FXy`z^G`_K&8+Iuw7l--(^2QBegLm+WFkBvb zvp7|H><#(eo5fML2Jo{D7Wdiw*rMTG(NSeC;hhG>!uNN@ajtrdcisZ^n0}M${nq8k z`)(HFMkuF2{MktE0W)))vUfs-uIHZ}QCaC*WYbvDFl=kDmjsWOrQ~J$7I`7O*dB3v zQJj%#1UV1bf^~LwAkP@msuY@&&YSB z;@g}X>wDGvkMjAk;y|Ag^73qv+;1GN_fV5O79NAcZZ71tgOa9@uQ`Zl{GV6=_*fJ2 z@|D~8Ji8h5punHi=a6E&eOVEQ-x0IoQ|Cc z4~9%cC^dpGSO`Q->ld8{uaaQ8`mRrHD#vr75CvWpO`5-Uw?^Z}P@hL}MRZO|IH5 zue?<>*_LAQOS}hdY*@0`;=I#s!!4z2l@V_grgJ;5SiWfqT*AA81^+~1emwIXnx{^` z#Q7CNjX@b`aehoR6<<+;Dc{PAnK1Oa8>T2c6r%T2 z&Wsair?Q66O1P)W_EU{H1%1JJdDb*Bx82_w{^VFO_W*aWb2MSk`v>+c2Z!q1!zd zFJ1SfC9PP=S?&?^pfO(XaNorq9HG>%NV8?(UAbR5r@HGPZ;mGKPEBqc|776JPs{j3 zll_oJfaslfry)0vcegv;-7Whe1J}>hWzWH*Gc*dmbF4CG7CXoCA$s-6%yvNzenJdX z?|s7avgg6A*S>mb<(H!FFWij?dG!l-{C4y*ym~P%OI%x9248$!kN-Sxz8>GZkugW4 ze$ol+Bs_-^zs|c)le>)Px*xLS2ji8!LL=neXUJW~v%G+3S;;p(&#b@*dEa=hmM;rQ zX5B&yyU=ozAwwa;hpt6q1Dit?d;*kOJNd;QxY&s&*|#cWw?@PM_wKKS(D zd=)G0t4qaT`RpVt){|e6FGP!j&QA_S4g_}V79V~RLp{@;t08bec~+R=~KAS@~z5jyVVs)ZKk!yc8t$kdX@I&^>uTK%pH?hsUbVf9kr*vzf;pM>ZlXqm-M)KL{zKu#@?g;6P5hy)xv!Cg1j_Z9HQO_Cl+&h_>@<1y{Qa0CB3^5FV*Vpim24} zbVXDyX(J)>$t@gI9$QIqFYzXuC#`==j-D!p`*mU_;`vva=IageQ&X`?du@v>PsL$t zt?`4y{wvVh4|BYPm4R|L}C$h*E8zt%qAp`;7{UOwD*)`#Qm!0mWF>(`Y=u2crWhVGK=Jc z)5Hb+7VS_aw z_NcBlz~GYH`%n}1NFX7PVU`sRQ&u59|L6QBG-1DPoC64dB zsCjJt@-cUDs>+6XPJ6!U{ zo9sq!|LUt`$Zy|V8$SWKJH zlAz9S-)vM;C%?(VIS4*7<ti^^S*nP(v^cFwgaO){Q;GYmUd z?%esnf8O}qi+fCE{*K}6MSq+8OPm-cjaV-ao+k$TfANztt>DcO-%na{k2{aZcgz#R z#}sl042W24a8egzJDF^8#$m&XluvBr+aTowJ$@-6daeB0JaL}Eqy)&m4Yz^(=<@$H z_8o9h9o^e|XI2D60YwBwnyA>Z#9mMVD+(6uii#Z&6|5-eqJry2Eb(Zf5o?Ss7|UvG zU_p}@#S$#hgczf1Fp9B6eJ$TJ_uhp?)_*>J%*@`o^GrQ+=FFLL@#gyl^uC+q;re^B zEH(`=U7uT2;Ks|Tad&8JYRsbE-KFZq^dH|{YT%kywut`Eih)tk|Icw`>@ImG?NQzt zQv5I&U{6e-=m!|D!3}zY7@$FrxtNn^E*Dc9TREnJNusyOQxjw^9+}%QUld$sR^Q*l z&jF?jiE4!wjFAJ2^C+nI`Eo#6R?a}cNL0YqqLB3wQXH`{Ey63^X(zcgq9IJ9!dixz z5)lyLYluF48l6FScMUTc6t1s4qh1eVuoDt{beL(+GP)Qhd3mlKQG$U4f`NFL)dgMk z^rVMWHOa-%S(0azZm0;29uhzHo(5_yCX;T{x))6*y~VKiXf^_3kE*6A@sa~r%yb+B)KFkz~@`<1EVWtJxf1r{| zir-TBhnaRRK}@Ix=nwF;eu+#VEOoG~!{hB1iFuDZd523?Sck!~($xMDl!1C?&<9VV zVc}9M=>So<)XzRbcuNNT)0y-?HZf|1-4=?{riQqnVsxI>()u* zyaIp@>x0^MLW7MNYwnqdy2b!g&pzb4UTPr4P}q9OwU$q!D5Jx|7xC2%As93SR*=4+ z{2!w58-nhga9X+^RQt;uH&mtAAnkLd za>XZ5WvJdi7JIw=0B=E*x*Ztx!ljT^o>#nNh(jdo3J3c zv^zD~EE(9j1j^nFO-(K2eiU3$s}$5ErNrQz)LWLow$m_lPg01>8L!N$v9LqxB( zxucSVyc;YT-Nzh%{}^hv3;m)u#qN@NunW<~BfF%!5=v&gzgzlJ@~FG#?fbMY=;bZs zA83u<;zwD7E@}^L{6@OoWOO^w1li8&P-_~^9+V}I6{=itl0^yM}G9iV1@CbQ(NQEebQlxooz`~4oDlTeTlby ziUO}ZDL;BcM*XoBT{!?j;p`q7eh_UE)Iw2m|2v9S9F$hGRn4ic5i0+-ql}?O$(eh< z`tGgZQeD$r4J#dvpezwu!^&Mp#SdG<%DQ`K-gnYQw!Vp?fXW4>mfUkm}p8`To@Ij1(-DA>A3NrQLO` z!l2>5t7ANU2D_Tr-CA@r2epDL(O%?8izR!ScUJmc@~6mi(sj1Q&)E8VX@kV}^fLbP zgEUHFNj}u!yi{GPXB>83I?dS^)fIgAZqy-Hn#g|bN;`9<<1Bd*O}Zqlu6m@JMHaE+ zz)V-MU5wQ(OA94-KbSUMk=nC2?sVsh6va9QQ`f7~Q1(ZVaobgCk;KxgP{TZFEz9ae z-{)aPE3p%mzb4f{-)?*jYZE@oBg=Lr!!@Z{{TxuTGR%rmtMLonTnCL~$}&_9MstJS za=kH-{=O!y;%zD`+^14uAiBReky1(LBP(7{SEe1;u}Jn^AYHtUsrLz2dVF1~Yafe% zV($EfE7iCm)vw-C0OvY1et}IBo-#-J6$@W7M>^?3(FnpGcc9O2NcC7`7M;C;g^Oh! z==BXLmmOC#mT#(kGG^P54C`!w$Ej++U>kPKVkG zRtVummOdU=q~C5~gz%%N+tL}fZjrIxuTpu&wv;!v&X>m6GE;zYqe(jDF4dqhj2YQX zSMua+GJ9Q_mT~qMR_14G*&y6^ML%maYD%D%vOZ#!PCxosp$h6YYlU-j{J)j14Lpo^oi{BI<3+-gRp2WVzzygUN_|#0V2a z{6U#pwZ!|4m@$-m|Jsq3*|9!snU$7)M==!89Ub?k_-MQoRprJ;9sDZrjJXkOP* zt{Zt@gg#JS&0kkZaC|LJRh*aq)HS7t4(9Yr zw67wIV5e?Uxk{{wPj?%+y+w#;xQEdsJ{_qvvue|$O00&5jR+>I5RugZCJ&?wZH?GF zI^`8LaADr;hceXLg-w+r=%foIz# z)iz!t&7-*VPffG&OES2E(#F!auI!0dw}1YxXpo^QYy^8DQ4KdXSo)mixv>t?U^?!` zI-^CHJ6qz~c50 zp=Ln42j?4fGldWsY(fKZ)Ac+GsmgpSZ9#Nt!FOAobxaXry5UHoGtH^WJftzSrYfuM zK0v$&v zsTRVo%mvnxUo}>%@=v%$5c$n1D;f65S%XY^oJLe*m6QB`#+8m)2~E)3wDA=UxXAkCD!yNlYg_=-D+g`4@JIEMFcsA_dq+anM6 zG@4dpnvQB=1vdJCg)hVs8Ahz$3u#z&7FhNHCV%hu1ra_QP`~U^Ej~o2UrN`ivwDq! zP^)@znMT4e+;@eXt@CsrHSRjOdBr9%+ml3 zc!KC>V(vP?ROgcLwH+|g-t;Mr@Me_(QuEty+Hj=S> >& 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 z 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 delta 79349 zcmb@v3qX|B+V=mf;WQ}9n5R(@M@2&=9ZeGz4UY|lHI)?=8n)G_tf;8iWzCpc5K`1= z50w>mq_U!|78M$9t!T&In-06Etf;IW!;H$5jO4r4v(^p6UGsnU|NXvs-^IPw?_S4i z-Oq8RZB=uhrjK0ZsRNrvMH}?VG}4WDBhyGShTMJf`GJBTqihdZcl zWZe^0z4BSg@^-}|RQ=*dy4z}9y{V_22^iGSfwCO1K3O%WU((%H${FE9)~SldSzi|} zlQsX_iRx|)zuhjyFjAUSPu#ZmP`tgz)>p)6(Jwi}|6<10pPy~`@W%Cz)gNjm8W?Jfa*Ok1PUPBuFSh^Y+kdZ;f4AmE2Zi_`^hL}-@u~gyOZ)GB z`|l6--^2Fb-|fHAp+2z#MPK{xVEgY#`|mjW?l7C~? zVw3yay}GX`v0s+h>{^B~7}jHC&NHIXopWv<>6E^)zZwG? zj~&{ayL!fK|E#);PmPW~zt;8mgbRmUe)dgSs36lxp@qGM1l-8UsT_wypD8*s12$9V z$H2;???j~eNOBnYnT<3R=@g`yNc53M*U3oo$eXO;NLY`~YjgKqF@dqj%LR`Jb7dee z4~bMHFVZxm1xWLd&s$lt*#)@l}FI{G=xZ#>h z3zr)!mS1ztRo56R{<89_YqwtB+wa&~-RBn(pmQUgny6f)&z;0)J!Fq%1R4M29>t3p zzobI(O2(@f$fX!YmEr+|1_&-!j%wE6HqGl8Z`I{`#%X{?&J!H=z)jsaj;2QKXzu1P z4CjgJ@Ua-b;CZ_rgC;-=$kFxNSp7?MePfd9kLugwVSK~I99~^^Gv2Hx>S5f|ORX%G z@l4IBKN^@OnvVK%{LU>X)HBUw9g#AA#tU>ghjE%{9^*98{4Q>fZ_rGsLq5}@t_HUD zYxP8fjMGZX8L!sm3dWlg0!q3{=AfBYF-{Yu4*5iDSpAK9qIHbZMC$|0L3`84_!GLL zCdR9E&^0qo{aB2@srzZwJU}a`*TB#n67TGo@nCPIoQv^l9i(o~^>Od%&I1M=uc_S~ zM!6oq%XmS5HE=rP)qNGuWW2nO;(o>}>@wnyq@oqm%JNtPT3J5ldSwL>coE~Yu%P5J z{yPWmr)FBr8sr_Pcsb*(dSz-QUH#C4Dp~#gdhe^kxPAQ59#@As?DK*y*D&5&-w@X` zo;q2b;TjnaE>R0;`Xg7zfAb$5_Hk}8UamW8WxP`JHpVHo3_USYI`1MiA?M^k_j58e zaIpq-adI>6)B}1LPv36O-ylV3VA>il)mQ7s-suMEMFqMkJ%-WY*5F;2=gGhXl)6?9fOm-|oBwT3&uTXlmr#;LxcSJJrveWye#Bu+u+ zV)gHba(4W=nS-QDWxPn2y^NDW=@EEl1n!#>t>T|%O49jR17n$zE{AbaB9Czjx_ri| zenBT!;~Nxov_hH?Nf+#FppMHobkG$uPRdm{ZTS^ZzMa&3xt-T#wx&Xc;IlBYkQBzH5u zbA#fkjJN9tk95Yby;s%ub>rB7a^!S#7)IX1szE;EuiUSA5#v<9nDGxbsrnU+-}*SX zjK3=8sMjm2Vf+i-Q9a{(o>BueG5%YX;uhmcx}P?1JN{|U;_g!ooKw4R!KGRWH{(}4 zq3WkHe&a^P(;3fyP;p-ux5qdBsaKHG<*;iGcuqCQXS~-I#fuof&Q!dZ@prYN6^!4s zMU8J%F~@uzbTy2RdqxdV&-hP|DBi^Q%{u5T#{Ihgwg7Vs*DH3Wci)29m1+Pt1UU)_|^x zsf^QA(aShp6|q8lk7$eOej<~&KK~e#UQ~POBZn$>-v8$?UZu-}hw*YKXUAVE zbC7iDj2G*2CgY@#F9P>R;5joQ-2dmX28|yp>GByTB?=fP<%$@m`oT`Fk3W)*R!Cbw z(v^2MP{(DR4!R1)Nx3S<+jO~_@nAr|xv1d|&FdoYdd4Z}8X0f=R4LcQI4KcmW)70h zVw@VZGEU0T3b}GdM)xz|5v`n4@ju@GyIF&LUG^|eL6;hVdn54lZXEkh(q(pY;7i+P zC7q9PQo_$TDVM`I)z4$xR!+trt&mGsz#5ExNd;dK;}mqoj5q0WIpd^U1-KpmG%z_T zBQ&UDoRq6(y!C6PTn*!-L|qqG#~(>o-{r8EN(~wrr=X)1(gNskZD#e))XG_myXpO3 z8*}((s;D)zr2$AGX9Vtwz}<|Ka-INlRDP?ZOJ$st@G?%yr87?TGZ`o4Xn~#g|0JEC zH_+1MFit_2&v=b47cfrB6?NwUgB-!`j!s_8I7wH|c%`M3t6-dzsAOCzhxn^v4r)-% zI7vqRA0PTDf}0Nx3G;W&Be|sk2njHM0h!kQITqM&NCXlX8Y0M~-VBfBx^3 zbj~n`JpM=t7vmIkZpNv;hjCJl7Dx+*a_ac^vId`P>Czb|>3ob==(3-2QZ6R~&zr^W z|EWPfYe3Q!FkWy_DObceDG_9xlq)8#*54&{IXR^M_>gq8!Z7Jm+Xx;DnCzM&UF3XyWOK;m?` zy6pPUpByJ^>D;U%k}j3;B3C7eIs&g@oRq6$yixrG z$Efe*`uHR18ao~8Fe2%i7^nKpjFWP-LatmZtN&O)OV`F6B%L!;9iE*X>9UJ)Qpg>F zdm`{aDszx@UdHo;{p%$oopDkklW|hc$2isZE8cbgPtxVE1{8F8j5liK@);-PiWslg zOHUrsl& z`p%bCa5sV5@lOp%I*T<(pRIzhm2pz8Edn=mJL;e6JG;0#{zy7km&2Z7W{i@~%{VFH zVVsmpWt{4J84oTGs2`ot3TdVkbeXI{rk2jfI4PIIc$F^aF-}V42O>BMBJd){Nx2~7 zhEpk5%s44gu6gJEKS@^+p+P0%6m+ygu3R;%U!;|*>COY4Z#e6_I}G<66?Bb^lXOiH zcyk19F|L$D{IxPibAKgW8{;IMp(jQwCgq%rQ+*fXq@2A#8UNG~1)YaAcv?%B$~XmG zI^)HwpK(&6fN?0Nj{hR&pawz4Njh2~ zSFW7Z|4b`a!8j>bH7C&h^4g{=R^d~7ux;)l^q@xv5eNwJ~)jv-wSHw6eR~*je{x@k* z9_|P`{3;^w$_TuQaZ;|DaX%EYGs(02Fl22;9}l_3=jy+?@^;{Un`-@ysL@T&bK7Rou%sDVNT8bwK}k zG?O_VKq_%VC_9%V)esmjeaNK}r-w;K2yIm~m3BoN-UGQm8`luKWLT z?Wkl8NV+P_18&DZ4M5V-3W-zDd09tiYUR=yr=asOUZKnW zF0PJ0k}jvqVebj?ya+s>aZ;{;@zxP4=!zJxUZC&)gUmtF6*ErKl`~Gt(F(b8m8||c ztz1=rIY_!1#+?gP(A6P-30{1aa%J~@&j#kR$FiuM3 zNiO4`R!q|6M`%#MI0YT8kouv+HOT7k)5;Zxar^iq=_%tF$@vr#@UjLZ9j%ZiO3G!j`dhSeKE_G8oKCKfzbXy#IvwixBPH@9@PY`u zh;dRb$hbE!SxHyS9Hc}!<0M@L<5a(raZ-*J$dwCJvj)vtx*Enwx_ZvDRM0gtPRcb! z;LVD6-TzYqi!~tWS{e6FQOdP3PD&VhVzgpX&e@G)|EYnin**n0{h!mg8KC^5 z*(<~Xsee+gysN%C{?69YRdhLQElIj6#@lqcnsHLDCIYYf1K0okZ#{F6bd8KxPE*n~ zF-}S}Gfv7`j8pyA0CSLZv_dYOaccJ)joYiBp5(P1C|t_a-CI4S30yl$3KE|qao!h0IK|0n6vBQ(fl zoRp&#(#l9VKdV1tr@byChqzjQ*UP4Sa;SPnjV>23PC-``fd?b-V#Wg`T{&}9&sNe^ zaIRliRx(b?RWVLdS2M1a3v{lKOIODlEYi}|GfqL*#CU493c6;-Njb~r(4Ph-M{9%z zZH$w0h8~FeG3F}eoQ#tau5d2*pQLk#JHV-dhj9uzS|JTgLFZ-l*K6g{8JEi0@#kZX z3SIUyP738j;CT^vKI5cZK{mVpcWLR0SOZcb$T%ri%sACAXPlIy1v=IGyB@(RS%aNg zx+=yg=xP{uE>uBR$2cih&pG}6zmYri@Bf<^C+V6QcV(%dvlu5OS{ZNF^#g6pK@AMO z5(+Moj#fyVg3iV2w`k?ujFWPyig(@r7wL*#)_|l-kH9k{a3ABOoWC1K{I@Pt(&cn> zbRB+qj8o9%Gfv7CFiy(R0=p?EkG~*mF!VKb@-Jqbq^n@u(&b9VNx7;Byc*m-{%JFOBIJ6$PP&p0X3$T%t2)Wz-bJI{2@T@G6Ul8#nL6Q!VQW%aYPa&3&0a?aD$ z;n~^G>H7NbVh&Qm9f5ly@KnZ0IWObJ8A`eI0CSLZnT(TkKE|oOpK($yhjCIaPxH?E z|Fv4WeAa-ZD`LD}mxGLxa>WsNd3TQY|N2Z<(cRJc>(@%gtF?4hoa-BtYR3J3C3Ows zN;$+|9dnR$w8G9{p`X*S`ZZd)CdNrQi*f%MDhOL8$M}D^0cm3mNFhTvqzMssGEPC~ zV!Yr?rGz_-%keu8KTnuLa?jbSK`P@EbY8|uxpc-U=rS2kt<%?EA9Ij&Bo(cgI?7@7 zTlE&?F-{XHh`@`^VE6wdX^=G_UL1j!GfrDj!FZ!qw34{3f7ljOkwfZ_PxCoy3#u8X zEvR9fwxFJIw_gQaBjcU_{cjU<(2AQQaEozTaVz6_=c)y?F|HSY_3N3@9@FvaWSmy) zVw_e?E2MrXRy?eJew{6>k!o{{PYo#O(piIIUCv~j6!Jyj{s=rLoXhGHxIlAouf z%V(UFC}5nf5k-ts{UGB~IXnJng`@;YSI!!kTDl6xNx3S<+jO~_aZ;|v&+h-pQ5T^> zJ>#TYBjeulm2yptlM>CH++KgzBbe3cP(eZsS{bLHqZM-Hj5E96XuPA9b29D@=>Kge zH*@6cvWIa}C^Z82M&Rj;lX8Jf=5Sr0r1LROO86P42Z)$0 z*rm3jh;a(KV#b?vxtwuQuA&>q{*$A!o1=?YF;3D|Gv2I~t6`j!sAJq#PR3t7b5Mgu z#z{I_ArHD{R{un;oW(dP*9LCKKXv3gOGT}rEpbxF8G*YZa5v+moTrPc2eq+>GBz`(d7chNx7m3JQ#@JC}y0b zD`%XPt6-dzsAQb35mlOZ-v3jBYSw_HqZM{Zr(&g!)qg-MSI;;p*VLT{jMTGLN6pc%4?Lh;dRP$T%ri z%sA?+>}*A8wBI(`y^!B(6y6k0~ z6iSc4Gyj|i3?FllbbiL^wR#TYq(mO$6mf{WF_9>z(zRK`haFXN<~T%aBQG*b$?OxD1s zrSmaP%H=R#rOSDYlS27jTpfSpDClz7n?<~caSFO12KiE_sCF4DjMsbCIj zP{}w+M=RvYRkQjxYvpPfC*|q`+;N_cUdBm@rU<+_0=F0^4C~(^|Sz#!0$##*1}1lW|hc_op23=l@d&z7uAg zq|0Nx@n)r5KI5cB0pp}xk>oP|Nm6PMWDQ6b6~^u3uT6vM zFo!(;=*(5aI7wQ^IMuJ`Tz|#W$aw25QR?__Vvf9klC+s~{Xaf$F;2?0GEU0T0=aTV z4!i&Fx5qy37|tAZ7*Ye0&dqqfE_)a!g;FDMFLAa0u6w|Aa;SN9K7wU3UVW=l$j7<< z7R=8$1zir~0g^6{IY>HMAt^yYSHSAe*UA+!PRbQC-lWR--~VS0lCB~GuZ+N}7^lOv znsGz^yJGm%*c|%P%1DVi)_|m|XPoLcGEU0r6?XPd$~A}Am-~N%md*-yfRl7>jQh@4 z!D(nq3&>ojR_0_pPjeUJL2x_%+{{7Jc^I$M9i=i(|LLZeaZ)awaZ)bxe0Kj&($NZO zWh9-Sb@Z%OE{AbaE}!ulT`uV4`uL-uE9!K}5a~KBgAsT!+mBWA#7M$_46~gQRO>JoN$yRKe%t7JS-sRyWH4(|pva5dv3 zT@B-;Tpi=294(M37cd%GgOjv$O^lOt7USuQRS>o^PBGLLfg5%^=ub1E22R}}agxr( zc&k>(%{VFHVVsmp4d-(Isew1#0Z!7<3TZ-gxMs5YS8C;ajFWOXj8~y-$6p?E(1S!i zb$v7z|S13PRx#~`?kH4q1bTyq0 zyItoaSUu-URL6~slX6XrlXA_R)9?Q+<{;@>8E?K%Db&Vz-2;jndSbLnG9J{tm~o?4^;6Dxp5_&d7f3G0uVjv(ZcxQ|rRLR)7wl96*Dzi^ zMA%VW$9TEs@}If4mr+FpgUq7JgS25mylj7A~TpfS) z4=P7Zm&2aGSDM!`{?CV0{d&fK*SwMO8r@G5<8=?}|NWn4<|uzuNoq0f>51=b@M&e- zum9ECHpU;i->z^v8i?TVa{ioZke{b)Ec%uIE;xOPHJAU)xY9;>%EP#t zy5bd@2Ri>M$-RMW325F2|rxT^L7vsr{GE`Y=a4)rM^h z|)5jUc z`w)b6h4JIUxLb0$e%X=y2k7bJ32V@gAZ%(F?;pm!VSGRsPY>e*!+2&G54gh|zA(q2 zFzyfIgTr`E7#|YG^TK#i7|##mLlsy1Ul8U<4r@>p#)pORU>Ns=@!~K(JdBrz@eyIX zVyV9Wms>D0%uyNEASH}fh4E2gygH1J4&yap{P-|lM_jHyHOx^@4zMv{yfKWA4dYE= zd|ViB4&!NI+zR6-fGhoxX$^CX4{OjC#wUbvwzys=*LYPwpKnk%dz#q!!8ku-h5YR^V0)DTQ+~S^}$CT7hBJJ?0gY= z?eR~yKKewC2=%C%7V=g75IW_l+EBvNK~()c^vW|cLcc%rTj)RF^n@mHjIamrXgL0@(#B!zC)@<`Wh+x6OWH9hl>7IG{E5# z#?44sNS7j|A>HW<-MaOSK;f0b@Lw&A+Ut~+c}jU;=Jj1=;(uoMpx#}&kIBd{{qNcZ zItTj^4L%DBW9T|zd~>TXK7f^Z$8EwmZjH*zvP=VRRCbo;&!WzU#fnwJ#-q-_Td+pd zqq@|#yTxrvIM`ewj6yU#`3_+`0^j@S`1YHHk%@^VBJab*mffz}3|=jaBddgQKGM_3 zQ$KRRHJF$K6B)i%7}IVNMl|}0Lf;R-_ZG^VN`>)u8B#$0LAr3g2D&WSII5)CV64h+ z6jtXh)amRjjPrWo6huDyM^%vOJCmeM}|6qKU5rtiqD0F(QvOYc2@}F*|7A4smh1JcGd~wrPqY95%w`8 zX~WasSebk;cjMTXX8@A_g*|>x7|*{WjGpfb( zF9rE;z7j_2K9y>|>&g@RuWUefu(%xq{g<75^Q{_e^?qRtYZ1nkNS}W%jO_=6;rT}S z4#M}OC5)|KtMVtPTafNYn){H$xDjay(n=&h((Op6 zPn>koDcNVtnVlVKf7!kD+?T)XgM0YUNpDX&ZRY149lkGAn*Vi2hhtwy$5&u+Upb8V zNTZR`kYqV=U-xtaSUS>R!V$^N>G){QqSNMC{-L4DvQ6)FUR!a3d8X`H{9Gy@PZg z()UO!kz}1YNK24*BTYuS5ot8ilSrQ<$v)1GjWUvS`U*BT7QZ(dhsDa z0sNVyE4&H2N9OTQczgu=Hf#uKLPC@w@NZMdl;%=49=1L)$`}T_t9O*~JMwoU$+|K% zA)kQsZ=?aJa}(12NNbTYkS^&HWjqgG;7a^C2jDh<5_D7mTZQxzmtYJ_%Bl6zp3=Us*}_Ud;LEq?En8<>3>&eJrwvK zQ{ca;^uMgrjBU6YG4m=^mq2iA{qm1X^*pBoS(h2Bf zKGFcBT%_N?WStLTe?XcBRu3Boy90Iv?3qZik3~HYz({RK)kq&8eTnoo()UQUJ@7L? zS?Ot{7m@ZNZASV#(k7&LkSrwG$uI=jIHXBPGf=(>_8z1~NKYYcN0N1RAzg*^Po()s zc}Ox~rz72tB>VUVD*S-MaY7>Ye*u6KX))5JNbkXU9qdS?)kwdh+z<9Fq=U%Mfc+e{ zZU`ocv<;~Wsf{J>KcTUsb9%dpkPLuJ7nbdB3CM#E&~VzCpv^-c+5Wf*pl#9_nQwrQsG}Jv!ZoHXW!l z5#`gtk4L{}!`}WzM@Qfv9UWITbadQ}dL11i!N9wrt*-Yooq;yIUt5Cbvz2oVqaWU> zt%6O3T@RZ9y9w40y9IU`Y??3#@^6cza<}VAIB8K-dP@eAolyFj3grG-1?9eggW#vwYHcVbsjPyMUF*w8F6o z)`gdT4X}GrA$x)_yeQB03L^mz`8Q3(#9$L931cN}Hf#?3xv(2h-XuB7TVShEJ^&j; zxgGX>l+z}o9?IFU)v_Jzo&Ykdkl6>j9<~j33#=P&8EawFVB2B+ut`&dQ2?6;yB0S0 zBw=iUT?tzUn>`hZ$nrEOfVYtiu-UK~(}j@-yM6{H0GpeE`motE@sM>0nS@yw7&APU zTPOJqP#AVo5uO6@c5@3JH5b9Iya^9kL1fbKoVo?}0Bn=2falt1yzX3y=Up%Cfq*dP z!EOmc0odHtc(jB~!ZU9@>|WSIu$%B~>%r^L+_hM_w0JIF1-lZq9Cj7#W3U;wV;tD* z+tKj>WY*(>*n?vt=?*l6-2|Hln_Vi5^{@@FHL!bO8>Gdfrh(U}*>^%AslWzdOh&mD zHcwhS1y;be--kVhZPh%LqJ%U_fRQY!hrdY&2d3Wju~8fL#T< z2(}h>73|)ZFd#bEQiI@vUG*~B!{)vsjAm(HMGy~$qO}NC*ml?g*u6XO)CgO<6Gsp1 zmJfyD9D;X&X`f)`$Q<|-dk5Rl3~6CkeF14D{}Ou*TZ>1^lq5`SKPCiw08eadVK?FF zt{OJ`AdVW?q#w{RUi3D=PKI5DC%Qb?v>%})?A|uK(1ES}70M6A@xT5Uf+!g?$3s&# zY&)Llg0Nd04r7b7F%F{%c9j#izQa()*D)Ee4ZZN-1iLEHVQiAth1*})wBsDcA=vBz zcy7u>zk_fK?7_r_IE+QG4X{DjRY?xRn;AeR1p@)3jlw{%dtsx8W1v*rU&CgMaTs~9 zd&lC50ygOchfyud6Hp(v*6T2qWujgN1{?u9(_vJgTnp<(`M@j(&Vo?tWVDCfGTUKn zgH4*_F!lzJ$wkIF5*1;+uxWGA0Jau(Eo{;}tPC9{&BsJhZij7#UAX}5@TPhdY!d92 zQyfMc>NT9|FnlO)I?Z7eNWKsX!RDTheqhtifI_l-E*=8Bv(e#s4#R_2+wJE=BG`mn z%v9RNm?`YuB^VHP)l!F%fLGlsFNQL(xqrb#VK=S7vn}lUOYw*UyZ18Ghdls$Nb<`a zMhaeqXIz2$um=jzKWy#Q_}>DpMW*3uNQj9vT<0(vWyR~U64<@4sd!bs{sxDU1DjTa zhOim1n_#p5>cI5@6TcDlVYl4mF#6#YdhN{)BNKKn>>}8VTO39a?8<<{cubaqXa}3H z8lWAS1FJC*-m%x-iW$OgT7$-0c^%y4x&4Aqon+sbHy9#zMY{PRn zYP_gdjUa}th0TM_-HI)PJpj86cGEV*25k0rC^r$uf5wZDY$7VYga)t)H3%lyB-l#W zO)q1wr2iF24!a6AVG`=qq8)4lY%c8H9oRD1+B%0(EA1{!5L>WmH`>9j--9il14Z6I zteuF3z!sf|<3AUfTsXGCu9X$u#7tqcVb`PL7TCQgC)9(ZoD2It>?+t+*aq0>xv2jS zC^s1#){FAWw-NOyC%lUwgw1}>flEF3`#6kYGd{p$bU*@RqNiY{upZci579u{CNzM} z_y`SP8(`~Uw|tDl3U)7S(n+ZI30@e$Cc);wX22G~X2X`l*1~RsZHH}wUBB017*jDG zY@i=9TaZbG-3#l3ZHLW=UH_TG*Z_N=8Lu!Thi#JOFCZ!G-Y*@7I~@amg{^?ih4sVk zg)M^3*arn+vtcWxA9fq;D%kfW{|3i@y9D1rx@qX>0D=xS_Xy5huv^-&0?ChJVAzIs zC^a1`I)>f?f$hvWl`31c~ugE=+p2fGp}Gqmg5 zfsU$vp^v{E9J8q(?p(~P2MTwnkJUG(wm&YBrD=?4Z6K453Nc1*gZIO|hFAH(gjv$#Ey7#|PP zWU%6t(2EC>V{?#wOw}y1vt8q)pyqn8%9K#=??(*YhHTZSF1L)Gdf8-D=+y5=#~wm9 zXLRWL?-xwWj74nY0ZPWI{K$1L0xJSrW)s8k!sdg$50TSwvmL}gE_BPmv9W1!s5dF}+`-Xv)u5NV6Kxx2T^kqvehSt zl7CndFEz+rB#hrw#BKT^AvO)Dd2#5uAEw8yMAox3bnpkXLAH8nD7h80Kz8!Q!ni|) z_wv@n_%@&of59EI3X2V`uK3h=?Cdh^YUs>g+@aT7`^M&i_*aBJYaJb1jO;#{9qTB% zM3RvA$7n}FzU@rt0fF;Fcm6nfa5}P+@o-b9&jG1eNVareX4fo|?#n|v4~^&{J&&PS zbGhwlL^gY+ooz?9S!Lz(bm0}CBYH_m7NO|8QW%Hrh0OeqNQiUJPKd?c$n9ARZ_`!y zP7ZnbkxV{lzYm7^vOjXmwtzKW6&nBZ_@T|n<`#6tgLEGP%Kj89EK zaN|+!R`c!zF(7q5jVni31Yi9%!nn@vU;REIA8hiqBJ_FtsCfCvUURK5{u&zI+Anm% zp~RteAPEo%lG-0R==)$!u%mXZD-I2eclH7W`HMYf=+Q&{V>5snZ-RQG9g6v7V0Dlk{H_nsY|+M3O5ON?&>*#dH@IyhFgx#EpJIDfr?M>qS7 z_&{5)xHORRH$~|AwlQ(J$Tv4&qmW;Zd|smnCH^`pt_Jyic79Au{yTUQwTHO**Tgv2 zaoD_f?X8W=K)xLL*U%y#`F{A4Kk?V$z0quq2}foF(7o)Z-e69xGXQjGi!>u z*(v(De{^(?DpxQM-h4dCxisKDN54lqvZF≥v_7&LCj`;ypTa=kH@Le5sKg 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

  • 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 TpOQECZviGF-Xb!8JWT#SzzaOL diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java new file mode 100644 index 00000000..d080538a --- /dev/null +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -0,0 +1,352 @@ +package cn.reactnative.modules.update; + +import android.os.Build; +import android.util.Log; +import java.io.File; +import java.util.HashSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Native cold-start update check (NATIVE_CHECKUPDATE_DESIGN §10): once per + * process, a few seconds after getBundleUrl, entirely independent of the app + * bundle — this is what lets a device bricked by a bad hot update pull the + * fixed version on the next launch. All decisions come from + * cpp/update_flow_core via NativeUpdateFlow; this class is IO glue only. + * Failures are silent and bounded: one round per launch, no retry storms, no + * version blacklisting. + */ +final class NativeCheckOrchestrator { + static final String KEY_CONFIG = "nativeConfig"; + // Raw response cache for the JS side to reuse (§10.3): + // {"ts": , "body": }. + static final String KEY_RESP_CACHE = "nativeCheckResp"; + + private static final AtomicBoolean scheduled = new AtomicBoolean(false); + + private NativeCheckOrchestrator() { + } + + static void schedule(final UpdateContext context) { + if (UpdateContext.DEBUG) { + return; + } + if (!scheduled.compareAndSet(false, true)) { + return; + } + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + // Keep the check away from the cold-start critical path + // (§7 R5) — its result targets the NEXT launch anyway. + Thread.sleep(5000); + runOnce(context); + } catch (Throwable e) { + // The rescue path must never take the app down with it. + Log.w(UpdateContext.TAG, "native check failed: " + e); + } + } + }, "pushy-native-check"); + thread.setPriority(Thread.MIN_PRIORITY + 1); + thread.setDaemon(true); + thread.start(); + } + + private static void runOnce(UpdateContext context) throws JSONException { + String configJson = context.getKv(KEY_CONFIG); + if (configJson == null || configJson.isEmpty()) { + // No persisted config (old integration / first ever launch): the + // native check silently does not run — this is the rollout gate. + return; + } + JSONObject config; + try { + config = new JSONObject(configJson); + } catch (JSONException e) { + return; + } + if (config.optBoolean("disabled", false)) { + return; + } + String appKey = config.optString("appKey", ""); + if (appKey.isEmpty()) { + return; + } + + String currentVersion = context.getCurrentVersion(); + String rolledBackVersion = context.rolledBackVersion(); + String uuid = context.getKv("uuid"); + if (uuid == null) { + uuid = ""; + } + + JSONObject identity = new JSONObject(); + identity.put("packageVersion", context.getPackageVersion()); + if (currentVersion != null) { + identity.put("currentVersion", currentVersion); + } + identity.put("uuid", uuid); + if (rolledBackVersion != null) { + identity.put("rolledBackVersion", rolledBackVersion); + } + + JSONObject cInfo = new JSONObject(); + cInfo.put("rnu", config.optString("rnu", "")); + cInfo.put("rn", config.optString("rn", "")); + cInfo.put("os", "android " + Build.VERSION.RELEASE); + cInfo.put("uuid", uuid); + + JSONObject input = new JSONObject(); + input.put("packageVersion", context.getPackageVersion()); + if (currentVersion != null) { + input.put("currentVersion", currentVersion); + } + input.put("buildTime", context.getBuildTime()); + input.put("cInfo", cInfo); + input.put("supportedDiffVersion", NativeUpdateCore.supportedDiffVersion()); + input.put("bundleHash", context.computeBundleHash()); + + String body = NativeUpdateFlow.buildCheckRequestBody(input.toString()); + if (body == null) { + return; + } + + String responseText = runCheckRequest(config, appKey, body); + if (responseText == null) { + Log.i(UpdateContext.TAG, + "native check: no endpoint reachable, giving up until next launch"); + return; + } + + // Persist the raw response for the JS side to reuse (§10.3), + // regardless of what the decision turns out to be. + JSONObject cacheEntry = new JSONObject(); + cacheEntry.put("ts", System.currentTimeMillis() / 1000); + cacheEntry.put("body", responseText); + context.setKv(KEY_RESP_CACHE, cacheEntry.toString()); + + String decisionJson = + NativeUpdateFlow.handleCheckResponse(responseText, identity.toString()); + if (decisionJson == null) { + return; + } + JSONObject decision = new JSONObject(decisionJson); + if (!"download".equals(decision.optString("action"))) { + Log.i(UpdateContext.TAG, + "native check: nothing to do (" + decision.optString("reason") + ")"); + return; + } + String hash = decision.optString("hash", ""); + if (!UpdateContext.isSafePathComponent(hash)) { + return; + } + + boolean downloaded = + new File(context.getRootDir(), hash + "/index.bundlejs").exists(); + if (!downloaded) { + downloaded = performAttempts( + context, decision.optJSONArray("attempts"), hash, currentVersion); + } + if (!downloaded) { + return; + } + + // Persist name/description/metaInfo alongside the version, mirroring + // the JS side's setLocalHashInfo after a successful download. + JSONObject info = decision.optJSONObject("info"); + if (info != null) { + JSONObject hashInfo = new JSONObject(); + for (String key : new String[] {"name", "description", "metaInfo"}) { + Object value = info.opt(key); + if (value instanceof String) { + hashInfo.put(key, value); + } + } + context.setKv("hash_" + hash, hashInfo.toString()); + } + + if ("setNeedUpdate".equals(config.optString("afterDownload"))) { + // Silent strategies only: activate for the next launch. + // Alert-style strategies leave activation to the JS side (§6). + try { + context.switchVersion(hash); + Log.i(UpdateContext.TAG, + "native check: downloaded " + hash + " and set for next launch"); + } catch (Exception e) { + Log.w(UpdateContext.TAG, "native check: switchVersion failed: " + e); + } + } else { + Log.i(UpdateContext.TAG, + "native check: downloaded " + hash + ", activation left to JS"); + } + } + + private static final OkHttpClient httpClient = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .build(); + + private static String httpRequest(String url, String postBody) { + try { + Request.Builder builder = + new Request.Builder().url(url).header("Accept", "application/json"); + if (postBody != null) { + builder.post(RequestBody.create( + postBody, MediaType.parse("application/json; charset=utf-8"))); + } + try (Response response = httpClient.newCall(builder.build()).execute()) { + if (!response.isSuccessful() || response.body() == null) { + return null; + } + return response.body().string(); + } + } catch (Exception e) { + return null; + } + } + + /** + * Sequential fallback over the ordered candidates (§5.1): one request at + * a time with its own timeout; after the configured round fails, + * queryUrls discovery merges remote candidates (excluding the + * already-tried) for one more round. No hedged race on purpose — this + * path is latency-insensitive. + */ + private static String runCheckRequest(JSONObject config, String appKey, String body) { + JSONArray endpoints = config.optJSONArray("endpoints"); + String orderedJson = NativeUpdateFlow.orderEndpointCandidates( + endpoints == null ? "[]" : endpoints.toString(), Math.random()); + JSONArray ordered; + try { + ordered = orderedJson == null ? new JSONArray() : new JSONArray(orderedJson); + } catch (JSONException e) { + return null; + } + HashSet tried = new HashSet<>(); + for (int i = 0; i < ordered.length(); i++) { + String base = ordered.optString(i, ""); + if (base.isEmpty()) { + continue; + } + tried.add(base); + String response = httpRequest(base + "/checkUpdate/" + appKey, body); + if (response != null) { + return response; + } + } + JSONArray queryUrls = config.optJSONArray("queryUrls"); + if (queryUrls == null) { + return null; + } + for (int i = 0; i < queryUrls.length(); i++) { + String listUrl = queryUrls.optString(i, ""); + if (listUrl.isEmpty()) { + continue; + } + String listText = httpRequest(listUrl, null); + if (listText == null) { + continue; + } + JSONArray remote; + try { + remote = new JSONArray(listText); + } catch (JSONException e) { + continue; + } + for (int j = 0; j < remote.length(); j++) { + String base = remote.optString(j, ""); + if (base.isEmpty() || tried.contains(base)) { + continue; + } + tried.add(base); + String response = httpRequest(base + "/checkUpdate/" + appKey, body); + if (response != null) { + return response; + } + } + // One successfully fetched remote list is enough. + break; + } + return null; + } + + private static boolean performAttempts( + UpdateContext context, JSONArray attempts, String hash, String originHash + ) { + if (attempts == null) { + return false; + } + for (int i = 0; i < attempts.length(); i++) { + JSONObject attempt = attempts.optJSONObject(i); + if (attempt == null) { + continue; + } + String type = attempt.optString("type"); + if ("diff".equals(type) && (originHash == null || originHash.isEmpty())) { + // diff patches from the running version; none is running. + continue; + } + JSONArray urls = attempt.optJSONArray("urls"); + if (urls == null) { + continue; + } + for (int j = 0; j < urls.length(); j++) { + String url = urls.optString(j, ""); + if (url.isEmpty()) { + continue; + } + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean succeeded = new AtomicBoolean(false); + final String attemptType = type; + UpdateContext.DownloadFileListener listener = + new UpdateContext.DownloadFileListener() { + @Override + public void onDownloadCompleted(DownloadTaskParams params) { + succeeded.set(true); + latch.countDown(); + } + + @Override + public void onDownloadFailed(Throwable error) { + Log.i(UpdateContext.TAG, "native check: " + attemptType + + " attempt failed: " + error); + latch.countDown(); + } + }; + if ("diff".equals(type)) { + context.downloadPatchFromPpk(url, hash, originHash, listener); + } else if ("pdiff".equals(type)) { + context.downloadPatchFromApk(url, hash, listener); + } else { + context.downloadFullUpdate(url, hash, listener); + } + try { + // Backstop only — the download pipeline carries its own + // timeouts/failures. + if (!latch.await(600, TimeUnit.SECONDS)) { + Log.w(UpdateContext.TAG, + "native check: " + type + " attempt timed out"); + return false; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + if (succeeded.get()) { + return true; + } + } + } + return false; + } +} diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java b/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java new file mode 100644 index 00000000..4afbc6f4 --- /dev/null +++ b/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java @@ -0,0 +1,22 @@ +package cn.reactnative.modules.update; + +/** + * JNI bindings to the shared update-flow decision layer + * (cpp/update_flow_core). String-in/string-out JSON on purpose — it matches + * the decision layer's own boundary and keeps this surface trivially stable. + * A null return means the input did not parse; callers skip the check round. + */ +final class NativeUpdateFlow { + static { + NativeUpdateCore.ensureLoaded(); + } + + private NativeUpdateFlow() { + } + + static native String buildCheckRequestBody(String inputJson); + + static native String orderEndpointCandidates(String endpointsJson, double randomSample); + + static native String handleCheckResponse(String responseText, String identityJson); +} diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index edffa45f..548165eb 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -159,7 +159,9 @@ public void run() { }); } - private String computeBundleHash() { + // Package-private: also the native cold-start check's request input + // (NativeCheckOrchestrator). Blocking — call off the main thread. + String computeBundleHash() { String cachePrefix = getPackageVersion() + "|" + getPackageLastUpdateTime() + "|"; String cached = sp.getString(KEY_BUNDLE_HASH_CACHE, null); if (cached != null && cached.startsWith(cachePrefix)) { @@ -529,6 +531,11 @@ public String getBundleUrl() { } public String getBundleUrl(String defaultAssetsUrl) { + // Integration guarantees getBundleUrl runs at every startup, which + // makes it the anchor for the once-per-process native update check. + // Cheap: an AtomicBoolean gate and a daemon thread spawn. + NativeCheckOrchestrator.schedule(this); + isUsingBundleUrl = true; StateCoreResult currentState = getStateSnapshot(); StateCoreResult launchState = runStateCore( diff --git a/cpp/update_flow_core/update_flow_jni.cpp b/cpp/update_flow_core/update_flow_jni.cpp new file mode 100644 index 00000000..e1e287ca --- /dev/null +++ b/cpp/update_flow_core/update_flow_jni.cpp @@ -0,0 +1,59 @@ +// JNI surface of the update-flow decision layer for the Android orchestrator +// (NativeCheckOrchestrator.java). Pure string-in/string-out: every payload is +// JSON, matching the decision layer's own boundary. A null return means +// "input did not parse" and the caller skips the check round. +#include + +#include + +#include "../patch_core/jni_util.h" +#include "flow_json.h" +#include "update_flow_core.h" + +namespace { + +jstring ToJString(JNIEnv* env, const std::string& value) { + return env->NewStringUTF(value.c_str()); +} + +} // namespace + +extern "C" JNIEXPORT jstring JNICALL +Java_cn_reactnative_modules_update_NativeUpdateFlow_buildCheckRequestBody( + JNIEnv* env, jclass, jstring inputJson) { + bool ok = false; + flowjson::Value input = flowjson::Parse( + pushy::jni_util::JStringToString(env, inputJson), &ok); + if (!ok || !input.IsObject()) { + return nullptr; + } + return ToJString( + env, flowjson::Stringify(updateflow::BuildCheckRequestBody(input))); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_cn_reactnative_modules_update_NativeUpdateFlow_orderEndpointCandidates( + JNIEnv* env, jclass, jstring endpointsJson, jdouble randomSample) { + bool ok = false; + flowjson::Value endpoints = flowjson::Parse( + pushy::jni_util::JStringToString(env, endpointsJson), &ok); + if (!ok || !endpoints.IsArray()) { + return nullptr; + } + return ToJString(env, flowjson::Stringify(updateflow::OrderEndpointCandidates( + endpoints, randomSample))); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_cn_reactnative_modules_update_NativeUpdateFlow_handleCheckResponse( + JNIEnv* env, jclass, jstring responseText, jstring identityJson) { + bool ok = false; + flowjson::Value identity = flowjson::Parse( + pushy::jni_util::JStringToString(env, identityJson), &ok); + if (!ok || !identity.IsObject()) { + return nullptr; + } + return ToJString(env, flowjson::Stringify(updateflow::HandleCheckResponse( + pushy::jni_util::JStringToString(env, responseText), + identity, false))); +} diff --git a/scripts/verify-android-so.js b/scripts/verify-android-so.js index 53646bbb..51c433c9 100644 --- a/scripts/verify-android-so.js +++ b/scripts/verify-android-so.js @@ -37,6 +37,9 @@ const REQUIRED_SYMBOLS = [ 'Java_cn_reactnative_modules_update_UpdateContext_syncStateWithBinaryVersion', 'Java_cn_reactnative_modules_update_UpdateContext_runStateCore', 'Java_cn_reactnative_modules_update_NativeUpdateCore_getSupportedDiffVersion', + 'Java_cn_reactnative_modules_update_NativeUpdateFlow_buildCheckRequestBody', + 'Java_cn_reactnative_modules_update_NativeUpdateFlow_orderEndpointCandidates', + 'Java_cn_reactnative_modules_update_NativeUpdateFlow_handleCheckResponse', ]; const SHT_DYNSYM = 11; From 2a4041ddfa2ea8bc44c1b8cc8742f587bb382555 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:42:35 +0800 Subject: [PATCH 12/29] feat(harmony): native cold-start update check orchestrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the third platform of NATIVE_CHECKUPDATE_DESIGN §10. NativeCheckOrchestrator.ts mirrors the iOS/Android orchestrators: once per process, five seconds after getBundleUrl (dev never reaches it — Metro's provider bypasses PushyFileJSBundleProvider, which doubles as the debug gate), reads the JS-provisioned config, and drives the flow through the decision layer now compiled into librnupdate: flow_json + update_flow_core join the CMake build and three NAPI exports (buildCheckRequestBody, orderEndpointCandidates, handleCheckResponse) join pushy.cpp, string-in/ string-out JSON. IO reuses @ohos.net.http for the sequential endpoint fallback with queryUrls discovery and UpdateContext's download/switch pipeline for diff → pdiff → full attempts; the raw response caches via setKv for JS reuse (§10.3). UpdateContext gains hasDownloadedVersion so an already-downloaded, not-yet-activated version is not re-downloaded every launch under alert strategies. The har staging script now stages cpp/update_flow_core alongside patch_core. Verified: DevEco tsc over the ets sources and the OHOS llvm toolchain syntax pass over pushy.cpp. Co-Authored-By: Claude Fable 5 --- harmony/pushy/src/main/cpp/CMakeLists.txt | 7 + harmony/pushy/src/main/cpp/pushy.cpp | 100 ++++- .../src/main/ets/NativeCheckOrchestrator.ts | 353 ++++++++++++++++++ harmony/pushy/src/main/ets/NativePatchCore.ts | 13 + harmony/pushy/src/main/ets/UpdateContext.ts | 18 + scripts/build-harmony-har.js | 5 + 6 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts diff --git a/harmony/pushy/src/main/cpp/CMakeLists.txt b/harmony/pushy/src/main/cpp/CMakeLists.txt index 3465b95c..528b3a08 100644 --- a/harmony/pushy/src/main/cpp/CMakeLists.txt +++ b/harmony/pushy/src/main/cpp/CMakeLists.txt @@ -17,6 +17,10 @@ set(PATCH_CORE_DIR ${REPO_ROOT}/cpp/patch_core) if(NOT EXISTS ${PATCH_CORE_DIR}/patch_core.cpp) set(PATCH_CORE_DIR ${STAGED_NATIVE_DIR}/patch_core) endif() +set(UPDATE_FLOW_CORE_DIR ${REPO_ROOT}/cpp/update_flow_core) +if(NOT EXISTS ${UPDATE_FLOW_CORE_DIR}/update_flow_core.cpp) + set(UPDATE_FLOW_CORE_DIR ${STAGED_NATIVE_DIR}/update_flow_core) +endif() # Always compile from source. A prebuilt-import branch used to live here, but # it had no symbol verification: a stale libs//librnupdate.so missing a @@ -32,6 +36,8 @@ set(HDP_SOURCES ${PATCH_CORE_DIR}/hbc_transform_wire.cpp ${PATCH_CORE_DIR}/patch_core.cpp ${PATCH_CORE_DIR}/state_core.cpp + ${UPDATE_FLOW_CORE_DIR}/flow_json.cpp + ${UPDATE_FLOW_CORE_DIR}/update_flow_core.cpp ${ANDROID_JNI_DIR}/hpatch.c ${HDIFFPATCH_DIR}/libHDiffPatch/HPatch/patch.c ${HDIFFPATCH_DIR}/file_for_patch.c @@ -46,6 +52,7 @@ add_library(rnupdate SHARED target_include_directories(rnupdate PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PATCH_CORE_DIR} + ${UPDATE_FLOW_CORE_DIR} ${ANDROID_JNI_DIR} ${HDIFFPATCH_DIR} ${HDIFFPATCH_DIR}/libHDiffPatch/HPatch diff --git a/harmony/pushy/src/main/cpp/pushy.cpp b/harmony/pushy/src/main/cpp/pushy.cpp index 617361f3..a32bf09f 100644 --- a/harmony/pushy/src/main/cpp/pushy.cpp +++ b/harmony/pushy/src/main/cpp/pushy.cpp @@ -1,4 +1,7 @@ #include + +#include "flow_json.h" +#include "update_flow_core.h" #include #include @@ -992,6 +995,98 @@ static napi_value GetSupportedDiffVersion(napi_env env, napi_callback_info) { return result; } +// ---- update-flow decision layer (NATIVE_CHECKUPDATE_DESIGN §10) ---- +// String-in/string-out JSON, matching the decision layer's own boundary. +// Returning undefined (nullptr without a pending exception) means "input did +// not parse"; the ArkTS orchestrator skips the check round. + +static napi_value MakeUtf8String(napi_env env, const std::string& value) { + napi_value result = nullptr; + if (napi_create_string_utf8(env, value.c_str(), value.size(), &result) != + napi_ok) { + ThrowError(env, "Failed to create string"); + return nullptr; + } + return result; +} + +static napi_value FlowBuildCheckRequestBody(napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + if (!GetArgCount(env, info, &argc, args) || argc < 1) { + ThrowError(env, "buildCheckRequestBody: missing input argument"); + return nullptr; + } + bool ok = false; + std::string input_json = GetString(env, args[0], &ok); + if (!ok) { + return nullptr; + } + bool parsed = false; + flowjson::Value input = flowjson::Parse(input_json, &parsed); + if (!parsed || !input.IsObject()) { + return nullptr; + } + return MakeUtf8String( + env, flowjson::Stringify(updateflow::BuildCheckRequestBody(input))); +} + +static napi_value FlowOrderEndpointCandidates(napi_env env, + napi_callback_info info) { + size_t argc = 2; + napi_value args[2] = {nullptr, nullptr}; + if (!GetArgCount(env, info, &argc, args) || argc < 2) { + ThrowError(env, "orderEndpointCandidates: missing arguments"); + return nullptr; + } + bool ok = false; + std::string endpoints_json = GetString(env, args[0], &ok); + if (!ok) { + return nullptr; + } + double sample = 0; + if (napi_get_value_double(env, args[1], &sample) != napi_ok) { + ThrowError(env, "orderEndpointCandidates: expected number sample"); + return nullptr; + } + bool parsed = false; + flowjson::Value endpoints = flowjson::Parse(endpoints_json, &parsed); + if (!parsed || !endpoints.IsArray()) { + return nullptr; + } + return MakeUtf8String(env, + flowjson::Stringify(updateflow::OrderEndpointCandidates( + endpoints, sample))); +} + +static napi_value FlowHandleCheckResponse(napi_env env, + napi_callback_info info) { + size_t argc = 2; + napi_value args[2] = {nullptr, nullptr}; + if (!GetArgCount(env, info, &argc, args) || argc < 2) { + ThrowError(env, "handleCheckResponse: missing arguments"); + return nullptr; + } + bool ok = false; + std::string response_text = GetString(env, args[0], &ok); + if (!ok) { + return nullptr; + } + std::string identity_json = GetString(env, args[1], &ok); + if (!ok) { + return nullptr; + } + bool parsed = false; + flowjson::Value identity = flowjson::Parse(identity_json, &parsed); + if (!parsed || !identity.IsObject()) { + return nullptr; + } + return MakeUtf8String(env, + flowjson::Stringify(updateflow::HandleCheckResponse( + response_text, identity, false))); +} + napi_value Init(napi_env env, napi_value exports) { if (!ExportFunction(env, exports, "syncStateWithBinaryVersion", SyncStateWithBinaryVersion) || !ExportFunction(env, exports, "runStateCore", RunStateCore) || @@ -1001,7 +1096,10 @@ napi_value Init(napi_env env, napi_value exports) { !ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries) || !ExportFunction(env, exports, "sha256Hex", Sha256Hex) || !ExportFunction(env, exports, "crc32", Crc32) || - !ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion)) { + !ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion) || + !ExportFunction(env, exports, "buildCheckRequestBody", FlowBuildCheckRequestBody) || + !ExportFunction(env, exports, "orderEndpointCandidates", FlowOrderEndpointCandidates) || + !ExportFunction(env, exports, "handleCheckResponse", FlowHandleCheckResponse)) { return nullptr; } return exports; diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts new file mode 100644 index 00000000..2ead7ede --- /dev/null +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -0,0 +1,353 @@ +import http from '@ohos.net.http'; +import deviceInfo from '@ohos.deviceInfo'; +import logger from './Logger'; +import NativePatchCore from './NativePatchCore'; +import type { UpdateContext } from './UpdateContext'; + +// 原生冷启动检测(NATIVE_CHECKUPDATE_DESIGN §10):每进程一次,getBundleUrl +// 后延迟数秒运行,完全不依赖 app bundle——坏热更把 JS 砸挂后,下次启动仍能 +// 拉到修复版。决策全部来自 cpp/update_flow_core(经 NativePatchCore 的 +// NAPI 面),本文件只是 IO 胶水。失败静默且有界:每次启动至多一轮,无重试 +// 风暴,不拉黑版本。 +// 鸿蒙的 debug 门控由触发点天然承担:dev 走 MetroJSBundleProvider, +// PushyFileJSBundleProvider.getBundleUrl 不会被调用。 +const TAG = 'NativeCheck'; +const KEY_CONFIG = 'nativeConfig'; +// 供 JS 侧复用的原始响应缓存(§10.3):{ ts: 秒级时间戳, body: 原始响应 }。 +const KEY_RESP_CACHE = 'nativeCheckResp'; +const REQUEST_TIMEOUT_MS = 10000; +const START_DELAY_MS = 5000; +const DOWNLOAD_TYPE_DIFF = 'diff'; +const DOWNLOAD_TYPE_PDIFF = 'pdiff'; + +interface NativeConfig { + appKey?: string; + endpoints?: string[]; + queryUrls?: string[]; + afterDownload?: string; + rnu?: string; + rn?: string; + disabled?: boolean; +} + +interface FlowIdentity { + packageVersion: string; + currentVersion?: string; + uuid: string; + rolledBackVersion?: string; +} + +interface FlowCInfo { + rnu: string; + rn: string; + os: string; + uuid: string; +} + +interface FlowCheckInput { + packageVersion: string; + currentVersion?: string; + buildTime: string; + cInfo: FlowCInfo; + supportedDiffVersion: number; + bundleHash: string; +} + +interface DecisionAttempt { + type?: string; + urls?: string[]; +} + +interface DecisionInfo { + name?: string; + description?: string; + metaInfo?: string; +} + +interface Decision { + action?: string; + reason?: string; + hash?: string; + attempts?: DecisionAttempt[]; + info?: DecisionInfo; +} + +interface RespCacheEntry { + ts: number; + body: string; +} + +let scheduled = false; + +export function scheduleNativeCheck(context: UpdateContext): void { + if (scheduled) { + return; + } + scheduled = true; + // 结果本来就是"下次启动生效",延迟几秒让开冷启动关键路径(§7 R5)。 + setTimeout(() => { + runOnce(context).catch((e: Object) => { + // 救援路径自身绝不能把应用拖垮。 + logger.error(TAG, `native check failed: ${e}`); + }); + }, START_DELAY_MS); +} + +async function runOnce(context: UpdateContext): Promise { + const configJson = context.getKv(KEY_CONFIG); + if (!configJson) { + // 无落盘配置(老接入/首启):静默不跑——这就是灰度开关。 + return; + } + let config: NativeConfig; + try { + config = JSON.parse(configJson) as NativeConfig; + } catch (e) { + return; + } + if (config.disabled) { + return; + } + const appKey = config.appKey ?? ''; + if (!appKey) { + return; + } + + const currentVersion = context.getCurrentVersion(); + const rolledBackVersion = context.rolledBackVersion(); + const uuid = context.getKv('uuid') ?? ''; + + const identity: FlowIdentity = { + packageVersion: context.getPackageVersion(), + uuid, + }; + if (currentVersion) { + identity.currentVersion = currentVersion; + } + if (rolledBackVersion) { + identity.rolledBackVersion = rolledBackVersion; + } + + const cInfo: FlowCInfo = { + rnu: config.rnu ?? '', + rn: config.rn ?? '', + os: `harmony ${deviceInfo.sdkApiVersion}`, + uuid, + }; + + const input: FlowCheckInput = { + packageVersion: identity.packageVersion, + buildTime: context.getBuildTime(), + cInfo, + supportedDiffVersion: NativePatchCore.getSupportedDiffVersion(), + bundleHash: await context.getBundleHash(), + }; + if (currentVersion) { + input.currentVersion = currentVersion; + } + + const body = NativePatchCore.buildCheckRequestBody(JSON.stringify(input)); + if (!body) { + return; + } + + const responseText = await runCheckRequest(config, appKey, body); + if (!responseText) { + logger.debug(TAG, 'no endpoint reachable, giving up until next launch'); + return; + } + + // 无论决策结果如何都落盘原始响应,供 JS 侧复用(§10.3)。 + const cacheEntry: RespCacheEntry = { + ts: Math.floor(Date.now() / 1000), + body: responseText, + }; + context.setKv(KEY_RESP_CACHE, JSON.stringify(cacheEntry)); + + const decisionJson = NativePatchCore.handleCheckResponse( + responseText, + JSON.stringify(identity), + ); + if (!decisionJson) { + return; + } + const decision = JSON.parse(decisionJson) as Decision; + if (decision.action !== 'download') { + logger.debug(TAG, `nothing to do (${decision.reason ?? ''})`); + return; + } + const hash = decision.hash ?? ''; + if (!hash) { + return; + } + + let downloaded = context.hasDownloadedVersion(hash); + if (!downloaded) { + downloaded = await performAttempts( + context, + decision.attempts ?? [], + hash, + currentVersion, + ); + } + if (!downloaded) { + return; + } + + // 与 JS 侧下载成功后的 setLocalHashInfo 对齐,持久化版本元信息。 + const info = decision.info; + if (info) { + const hashInfo: DecisionInfo = {}; + if (typeof info.name === 'string') { + hashInfo.name = info.name; + } + if (typeof info.description === 'string') { + hashInfo.description = info.description; + } + if (typeof info.metaInfo === 'string') { + hashInfo.metaInfo = info.metaInfo; + } + context.setKv(`hash_${hash}`, JSON.stringify(hashInfo)); + } + + if (config.afterDownload === 'setNeedUpdate') { + // 仅静默策略:激活到下次启动;alert 类策略把激活权留给 JS(§6)。 + try { + context.switchVersion(hash); + logger.debug(TAG, `downloaded ${hash} and set for next launch`); + } catch (e) { + logger.error(TAG, `switchVersion failed: ${e}`); + } + } else { + logger.debug(TAG, `downloaded ${hash}, activation left to JS`); + } +} + +async function httpRequest( + url: string, + postBody?: string, +): Promise { + const client = http.createHttp(); + try { + const header: Record = { Accept: 'application/json' }; + if (postBody !== undefined) { + header['Content-Type'] = 'application/json'; + } + const response = await client.request(url, { + method: postBody !== undefined + ? http.RequestMethod.POST + : http.RequestMethod.GET, + header, + extraData: postBody, + connectTimeout: REQUEST_TIMEOUT_MS, + readTimeout: REQUEST_TIMEOUT_MS, + expectDataType: http.HttpDataType.STRING, + }); + if ( + response.responseCode >= 200 && + response.responseCode < 300 && + typeof response.result === 'string' + ) { + return response.result; + } + return undefined; + } catch (e) { + return undefined; + } finally { + client.destroy(); + } +} + +// 顺序回退(§5.1):按纯层给出的候选序逐个请求,单请求超时;整轮失败后经 +// queryUrls 发现远程候选(排除已试过的)再来一轮。刻意不做 hedged race—— +// 该路径对延迟不敏感。 +async function runCheckRequest( + config: NativeConfig, + appKey: string, + body: string, +): Promise { + const orderedJson = NativePatchCore.orderEndpointCandidates( + JSON.stringify(config.endpoints ?? []), + Math.random(), + ); + if (!orderedJson) { + return undefined; + } + const ordered = JSON.parse(orderedJson) as string[]; + const tried = new Set(); + for (const base of ordered) { + if (!base) { + continue; + } + tried.add(base); + const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); + if (response !== undefined) { + return response; + } + } + for (const listUrl of config.queryUrls ?? []) { + if (!listUrl) { + continue; + } + const listText = await httpRequest(listUrl); + if (listText === undefined) { + continue; + } + let remote: string[]; + try { + const parsed = JSON.parse(listText) as Object; + if (!Array.isArray(parsed)) { + continue; + } + remote = parsed as string[]; + } catch (e) { + continue; + } + for (const base of remote) { + if (typeof base !== 'string' || !base || tried.has(base)) { + continue; + } + tried.add(base); + const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); + if (response !== undefined) { + return response; + } + } + // 拉到一份可解析的远程列表就够了。 + break; + } + return undefined; +} + +async function performAttempts( + context: UpdateContext, + attempts: DecisionAttempt[], + hash: string, + originHash: string, +): Promise { + for (const attempt of attempts) { + const type = attempt.type ?? ''; + if (type === DOWNLOAD_TYPE_DIFF && !originHash) { + // diff 以当前运行版本为源;没有运行中的热更版本就跳过。 + continue; + } + for (const url of attempt.urls ?? []) { + if (!url) { + continue; + } + try { + if (type === DOWNLOAD_TYPE_DIFF) { + await context.downloadPatchFromPpk(url, hash, originHash); + } else if (type === DOWNLOAD_TYPE_PDIFF) { + await context.downloadPatchFromPackage(url, hash); + } else { + await context.downloadFullUpdate(url, hash); + } + return true; + } catch (e) { + logger.debug(TAG, `${type} attempt failed: ${e}`); + } + } + } + return false; +} diff --git a/harmony/pushy/src/main/ets/NativePatchCore.ts b/harmony/pushy/src/main/ets/NativePatchCore.ts index a40a4a70..95038b66 100644 --- a/harmony/pushy/src/main/ets/NativePatchCore.ts +++ b/harmony/pushy/src/main/ets/NativePatchCore.ts @@ -86,6 +86,19 @@ interface NativePatchCoreBindings { crc32(data: Uint8Array | ArrayBuffer): number; /** 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道) */ getSupportedDiffVersion(): number; + + // 更新流程决策层(cpp/update_flow_core,NATIVE_CHECKUPDATE_DESIGN §10): + // JSON 字符串进出,与决策层自身的边界一致。返回 undefined = 输入未通过 + // 解析,编排器跳过本轮检测。 + buildCheckRequestBody(inputJson: string): string | undefined; + orderEndpointCandidates( + endpointsJson: string, + randomSample: number, + ): string | undefined; + handleCheckResponse( + responseText: string, + identityJson: string, + ): string | undefined; } export default NativeUpdateCore as unknown as NativePatchCoreBindings; diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index f8dc392b..eec9124a 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -6,6 +6,7 @@ import { DownloadTaskParams } from './DownloadTaskParams'; import { bundleManager } from '@kit.AbilityKit'; import { util } from '@kit.ArkTS'; import logger from './Logger'; +import { scheduleNativeCheck } from './NativeCheckOrchestrator'; import NativePatchCore, { STATE_OP_CLEAR_FIRST_TIME, STATE_OP_CLEAR_ROLLBACK_MARK, @@ -535,6 +536,18 @@ export class UpdateContext { } } + // 原生冷启动检测(NativeCheckOrchestrator)用来跳过已就绪版本的重复下载 + // ——alert 类策略下版本已下载但未激活,若不判在这里会每次冷启动重下一遍。 + public hasDownloadedVersion(hash: string): boolean { + try { + return fileIo.accessSync( + this.getBundlePath(assertSafePathComponent(hash)), + ); + } catch (e) { + return false; + } + } + public switchVersion(hash: string): void { try { const bundlePath = this.getBundlePath(assertSafePathComponent(hash)); @@ -563,6 +576,11 @@ export class UpdateContext { } public getBundleUrl() { + // 接入约定保证 getBundleUrl 每次启动必经,借它锚定每进程一次的原生检测 + // (NativeCheckOrchestrator);dev 走 Metro provider 不会到这里,天然完成 + // debug 门控。开销仅一个布尔门 + setTimeout。 + scheduleNativeCheck(this); + UpdateContext.isUsingBundleUrl = true; this.trace('getBundleUrl:enter'); const stateBeforeLaunch = this.getStateSnapshot(); diff --git a/scripts/build-harmony-har.js b/scripts/build-harmony-har.js index 06ddf70b..1650daeb 100644 --- a/scripts/build-harmony-har.js +++ b/scripts/build-harmony-har.js @@ -7,6 +7,7 @@ const { spawnSync } = require('child_process'); const projectRoot = path.resolve(__dirname, '..'); const androidJniDir = path.join(projectRoot, 'android', 'jni'); const patchCoreDir = path.join(projectRoot, 'cpp', 'patch_core'); +const updateFlowCoreDir = path.join(projectRoot, 'cpp', 'update_flow_core'); const harmonyModuleDir = path.join(projectRoot, 'harmony', 'pushy'); const harmonyBuildDir = path.join(harmonyModuleDir, 'build'); const harmonyNativeStageDir = path.join( @@ -238,6 +239,10 @@ function syncHarmonyNativeSources() { path.join(harmonyNativeStageJniDir, 'lzma', 'C'), ); copyPath(patchCoreDir, harmonyNativeStagePatchCoreDir); + copyPath( + updateFlowCoreDir, + path.join(harmonyNativeStageDir, 'update_flow_core'), + ); } function cleanupHarmonyNativeSources() { From 24f3adf72a3faaf70b687a2a7fc20ace025483e2 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:44:28 +0800 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20JS=20reuses=20the=20native=20chec?= =?UTF-8?q?k's=20cached=20response=20(=C2=A710.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the double-check window between the native cold-start check and the JS-side checkUpdate: getNativeCheckCache (all three platforms + spec) exposes the response the orchestrator persisted, and the JS client reuses it when fresher than two minutes instead of issuing its own request. The cache read happens inside the shared response promise, so no await lands between the dedup window and the lastRespJson assignment (the JS2-1 double-send lesson); absent, stale, or unreadable caches fall through to a normal network check, and dev builds skip the path entirely. Covered by three client tests (fresh reuse without network, stale fallthrough, unreadable cache); full suite 158 green, harmony tsc, clang syntax pass and javac all re-verified. Co-Authored-By: Claude Fable 5 --- .../modules/update/UpdateModuleImpl.java | 13 +++++ .../modules/update/UpdateModule.java | 5 ++ .../modules/update/UpdateModule.java | 5 ++ .../pushy/src/main/ets/PushyTurboModule.ts | 7 +++ ios/RCTPushy/RCTPushy.mm | 14 +++-- src/NativePushy.ts | 7 +++ src/__tests__/client.test.ts | 58 +++++++++++++++++++ src/client.ts | 42 +++++++++++++- 8 files changed, 145 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java index 6840c931..64e7b613 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java @@ -224,6 +224,19 @@ public void run() { }); } + /** + * Raw response cached by the native cold-start check, for the JS side to + * reuse instead of re-checking (§10.3). Empty string when absent; never + * rejects. + */ + public static void getNativeCheckCache( + final UpdateContext updateContext, + final Promise promise + ) { + String cached = updateContext.getKv(NativeCheckOrchestrator.KEY_RESP_CACHE); + promise.resolve(cached == null ? "" : cached); + } + private static void setUuidInternal(UpdateContext updateContext, String uuid) { updateContext.setKv("uuid", uuid); } diff --git a/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java b/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java index 1ddea874..e25d31df 100644 --- a/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +++ b/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java @@ -93,6 +93,11 @@ public void syncNativeConfig(String config, Promise promise) { UpdateModuleImpl.syncNativeConfig(updateContext, config, promise); } + @Override + public void getNativeCheckCache(Promise promise) { + UpdateModuleImpl.getNativeCheckCache(updateContext, promise); + } + @Override public void setLocalHashInfo(String hash, String info, Promise promise) { UpdateModuleImpl.setLocalHashInfo(updateContext, hash, info, promise); diff --git a/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java b/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java index e21177c3..710bdb6a 100644 --- a/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +++ b/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java @@ -95,6 +95,11 @@ public void syncNativeConfig(String config, Promise promise) { UpdateModuleImpl.syncNativeConfig(updateContext, config, promise); } + @ReactMethod + public void getNativeCheckCache(Promise promise) { + UpdateModuleImpl.getNativeCheckCache(updateContext, promise); + } + @ReactMethod public void setLocalHashInfo(String hash, String info) { UpdateModuleImpl.setLocalHashInfo(updateContext, hash, info); diff --git a/harmony/pushy/src/main/ets/PushyTurboModule.ts b/harmony/pushy/src/main/ets/PushyTurboModule.ts index 66baf4b0..9109b5a5 100644 --- a/harmony/pushy/src/main/ets/PushyTurboModule.ts +++ b/harmony/pushy/src/main/ets/PushyTurboModule.ts @@ -197,6 +197,13 @@ export class PushyTurboModule extends UITurboModule { this.context.setKv('nativeConfig', config); } + // 原生冷启动检测落盘的原始响应缓存,JS 侧新鲜期内直接复用免二次请求 + // (NATIVE_CHECKUPDATE_DESIGN §10.3)。缺省空串,永不 reject。 + async getNativeCheckCache(): Promise { + logger.debug(TAG, ',call getNativeCheckCache'); + return this.context.getKv('nativeCheckResp') ?? ''; + } + async reloadUpdate(options: { hash: string }): Promise { logger.debug(TAG, ',call reloadUpdate'); const hash = this.requireHash(options.hash, 'reloadUpdate'); diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 4134dfb0..401c8caf 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -44,6 +44,9 @@ // Raw JSON persisted by JS (syncNativeConfig) for the native cold-start // update check; parsed on read by the orchestrator. Absent = check disabled. static NSString *const keyNativeConfig = @"REACTNATIVECN_PUSHY_NATIVE_CONFIG_KEY"; +// Raw response cache written by the native cold-start check for the JS side +// to reuse (§10.3): {"ts": , "body": }. +static NSString *const keyNativeCheckCache = @"REACTNATIVECN_PUSHY_NATIVE_CHECK_RESP_KEY"; static NSString *const PushyErrorDomain = @"cn.reactnative.pushy"; // file def @@ -534,6 +537,12 @@ - (instancetype)init resolve(@true); } +RCT_EXPORT_METHOD(getNativeCheckCache:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + resolve([PushyDefaults() stringForKey:keyNativeCheckCache] ?: @""); +} + RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash value:(NSString *)value resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) @@ -1165,11 +1174,6 @@ + (NSString *)buildTime #pragma mark - native cold-start check orchestration -// Raw response cache for the JS side to reuse instead of re-checking -// (NATIVE_CHECKUPDATE_DESIGN §10.3): {"ts": , "body": }. -static NSString *const keyNativeCheckCache = @"REACTNATIVECN_PUSHY_NATIVE_CHECK_RESP_KEY"; - // Blocking JSON HTTP round-trip on the orchestrator's utility thread. Returns // the response body on 2xx, nil on any failure. The semaphore timeout is a // backstop over the request's own timeoutInterval. diff --git a/src/NativePushy.ts b/src/NativePushy.ts index 0a47c3e1..589f7497 100644 --- a/src/NativePushy.ts +++ b/src/NativePushy.ts @@ -24,6 +24,13 @@ export interface Spec extends TurboModule { * skips its check, which doubles as the feature's rollout gate. */ syncNativeConfig(config: string): Promise; + /** + * Raw response cached by the native cold-start check + * ('{"ts":,"body":}'), so the JS + * side can reuse a fresh result instead of re-checking (§10.3). Resolves to + * an empty string when absent; never rejects. + */ + getNativeCheckCache(): Promise; reloadUpdate(options: { hash: string }): Promise; restartApp(): Promise; setNeedUpdate(options: { hash: string }): Promise; diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 58f6d0fe..93a46a43 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -36,6 +36,7 @@ const setupClientMocks = ({ getBundleHash = mock(() => ''), // undefined 模拟旧原生(无该方法,feature-detect 静默跳过) syncNativeConfig = undefined, + getNativeCheckCache = undefined, }: { isFirstTime?: boolean; markSuccess?: ReturnType; @@ -50,6 +51,7 @@ const setupClientMocks = ({ supportedDiffVersion?: number; getBundleHash?: ReturnType; syncNativeConfig?: ReturnType; + getNativeCheckCache?: ReturnType; } = {}) => { (globalThis as any).__DEV__ = false; @@ -79,6 +81,7 @@ const setupClientMocks = ({ restartApp, resetToPackagedBundle, ...(syncNativeConfig ? { syncNativeConfig } : {}), + ...(getNativeCheckCache ? { getNativeCheckCache } : {}), }, buildTime: '2023-01-01', cInfo: { @@ -1413,3 +1416,58 @@ describe('syncNativeConfig', () => { new Pushy({ appKey: 'demo-app' }); }); }); + +describe('native check cache reuse', () => { + test('a fresh cached response is reused without a network check', async () => { + const cachedResult = { upToDate: true }; + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 30, + body: JSON.stringify(cachedResult), + }) + ) + ); + setupClientMocks({ getNativeCheckCache }); + // setup.ts's default fetch throws, so any network attempt would surface + // as a failed check (undefined) instead of the cached result. + const { Pushy } = await importFreshClient('native-cache-fresh'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(cachedResult); + expect(getNativeCheckCache).toHaveBeenCalled(); + }); + + test('a stale cached response falls through to the network', async () => { + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 600, + body: JSON.stringify({ upToDate: true }), + }) + ) + ); + setupClientMocks({ getNativeCheckCache }); + const networkResult = { update: true, hash: 'net-hash' }; + (globalThis as any).fetch = mock(async () => + createJsonResponse(networkResult) + ); + const { Pushy } = await importFreshClient('native-cache-stale'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(networkResult); + }); + + test('an unreadable cache never breaks the check', async () => { + const getNativeCheckCache = mock(() => Promise.resolve('not json')); + setupClientMocks({ getNativeCheckCache }); + const networkResult = { upToDate: true }; + (globalThis as any).fetch = mock(async () => + createJsonResponse(networkResult) + ); + const { Pushy } = await importFreshClient('native-cache-bad'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(networkResult); + }); +}); diff --git a/src/client.ts b/src/client.ts index 7b17a5c3..925dafe0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -624,6 +624,39 @@ export class Pushy { this.lastWorkingEndpoint = endpoint; return value; }; + /** + * Reuse the native cold-start check's cached response when fresh + * (NATIVE_CHECKUPDATE_DESIGN §10.3) instead of re-checking. Returns + * undefined whenever the cache is absent, stale, or unreadable — any + * failure falls through to a normal network check. + */ + private readNativeCheckCache = async (): Promise => { + try { + if (__DEV__ || typeof PushyModule.getNativeCheckCache !== 'function') { + return undefined; + } + const raw = await Promise.resolve(PushyModule.getNativeCheckCache()); + if (!raw || typeof raw !== 'string') { + return undefined; + } + const entry = JSON.parse(raw); + if (typeof entry?.ts !== 'number' || typeof entry?.body !== 'string') { + return undefined; + } + if (Date.now() / 1000 - entry.ts > 120) { + return undefined; + } + const result = JSON.parse(entry.body); + if (!result || typeof result !== 'object') { + return undefined; + } + log('reusing native check response cache'); + return result as CheckResult; + } catch { + return undefined; + } + }; + assertDebug = (matter: string) => { if (__DEV__ && !this.options.debug) { info(this.t('dev_debug_disabled', { matter })); @@ -774,7 +807,14 @@ export class Pushy { type: 'checking', message: `${this.options.appKey}: ${stringifyBody}`, }); - const respJsonPromise = this.fetchCheckResult(fetchPayload); + // The native cold-start check may have a fresh response on disk + // (§10.3); reuse it instead of re-checking. The read happens INSIDE + // the promise so no await lands between the dedup window above and the + // lastRespJson assignment below (the JS2-1 double-send lesson). + const respJsonPromise = (async (): Promise => { + const cached = await this.readNativeCheckCache(); + return cached ?? (await this.fetchCheckResult(fetchPayload)); + })(); this.lastRespJson = respJsonPromise; const result: CheckResult = await respJsonPromise; From 8ee69ae00ee74bc29d54720604683f0033d7b4d8 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 7 Aug 2026 23:44:57 +0800 Subject: [PATCH 14/29] docs: mark the native checkUpdate implementation complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three orchestrators, the provisioning layer and the §10.3 response cache reuse have landed; what remains is e2e coverage (the bricked-bundle end-to-end scenario), README/CHANGELOG and a release. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 165f7f2e..ca3c15f5 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -1,8 +1,12 @@ # 原生检测更新设计:让砖机也能被修好 -> 状态:实施中——选型已改判方案 B(三端统一 C++ + 金标向量契约,见 §4 裁决)。 -> 决策层双实现已落地:`src/updateFlowCore.ts`(参照)+ `cpp/update_flow_core` -> (移植),69 条向量双侧钉死。剩余工作:§8 第 4 步原生编排。 +> 状态:**实现完成(2026-08-07,e34f390..24f3adf)**——三端编排全部落地: +> 标定层 syncNativeConfig、iOS RCTPushyOrchestrator、Android +> NativeCheckOrchestrator(librnupdate.so 已重编 4 ABI 并过符号/对齐校验)、 +> Harmony NativeCheckOrchestrator.ts + NAPI,§10.3 的 JS 响应缓存复用 +> (getNativeCheckCache,2 分钟 TTL)也已闭环。剩余:e2e 用例(沿用 +> Example/e2etest 基建,验证"坏 bundle 下原生仍拉到修复版"端到端场景)、 +> README/CHANGELOG、发版。 > 取代:`REMOTE_RESET_DESIGN.md`(本地启动熔断方案,已放弃,理由见 §1.2) > 前置:bundleHash 迁移 Phase 0/1/2 已上线且 buildTime 永久保留作 fallback 已定稿 > (2026-08-03)——checkUpdate 的 wire protocol 已稳定,协议前置解除 From ae5f1ca6aadcfefe66d565cda963fbf867ccd2af Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Sat, 8 Aug 2026 21:43:43 +0800 Subject: [PATCH 15/29] =?UTF-8?q?feat:=20forceBoot=20=E2=80=94=20per-versi?= =?UTF-8?q?on=20remote=20activation=20override=20(=C2=A710.7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last gap in the brick rescue: under alert-style strategies (the default) the native check downloaded the fix but never activated it, and a bricked device's JS can never do so. The server can now mark a version config.forceBoot ("boot into this version next launch") and the native orchestrator activates it regardless of the client's strategy. Activation policy folds into the pure layer: shouldActivateAfterDownload (afterDownload === 'setNeedUpdate' || info.config.forceBoot, JS truthiness) lands in updateFlowCore + the C++ port with 8 new golden vectors (77 total), and HandleCheckResponse gains the afterDownload parameter and emits `activate` on download decisions — all three orchestrators now read that single boolean instead of comparing strategy strings. Deliberate semantics, pinned by composition tests: native-only (the JS interactive flow is not consulted), the device-local rolledBack guard wins over forceBoot, and first_time crash protection still applies to the forced version. librnupdate.so rebuilt for all four ABIs with the new JNI signature (symbols + 16KB alignment verified); JS suite (158), harmony tsc, OHOS clang, iOS clang and javac all green. Server-side counterpart (root-version config passthrough + console toggle) tracked in §10.7. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 26 +++++ android/lib/arm64-v8a/librnupdate.so | Bin 289808 -> 290208 bytes android/lib/armeabi-v7a/librnupdate.so | Bin 182900 -> 183236 bytes android/lib/x86/librnupdate.so | Bin 267000 -> 267432 bytes android/lib/x86_64/librnupdate.so | Bin 290856 -> 291304 bytes .../update/NativeCheckOrchestrator.java | 11 ++- .../modules/update/NativeUpdateFlow.java | 3 +- cpp/update_flow_core/tests/flow_vectors.json | 92 ++++++++++++++++++ .../tests/update_flow_core_test.cpp | 64 ++++++++---- cpp/update_flow_core/update_flow_core.cpp | 13 ++- cpp/update_flow_core/update_flow_core.h | 22 ++++- cpp/update_flow_core/update_flow_jni.cpp | 6 +- harmony/pushy/src/main/cpp/pushy.cpp | 12 ++- .../src/main/ets/NativeCheckOrchestrator.ts | 7 +- harmony/pushy/src/main/ets/NativePatchCore.ts | 1 + ios/RCTPushy/RCTPushy.mm | 10 +- scripts/generate-flow-vectors.ts | 25 +++++ src/type.ts | 8 ++ src/updateFlowCore.ts | 18 ++++ 19 files changed, 278 insertions(+), 40 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index ca3c15f5..51925742 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -367,3 +367,29 @@ iOS(NSURLSession,下载/patch/state 全现成,纯增量)→ Android(Ok librnupdate.so 进 update_flow_core,绑一次 .so 重编)→ Harmony。e2e 用例 沿用 Example/e2etest 既有基建,验证"坏 bundle 下原生仍能拉到修复版"的端到 端场景。 + +### 10.7 forceBoot——策略的按版本远程覆盖(救砖的最后一环,2026-08-08 实现) + +§10.1 的 `afterDownload` 折算暴露了一个洞:alert 类策略(默认策略)下原生只 +下载不激活,而砖机的 JS 永远不会跑——修复版躺在磁盘上永不生效,救援在它存 +在的理由上失效。 + +解法是把激活决策做成**客户端默认 + 服务端按版本覆盖**:版本 `config` 增加 +`forceBoot: true`(控制台按版本勾选,语义是"强制以该版本启动",不是 UX 层 +面的"静默")。激活谓词收敛为纯层的 `shouldActivateAfterDownload(info, +afterDownload)`:本地 silent 策略 或 响应标记 forceBoot 即激活。 +`HandleCheckResponse` 增参 `afterDownload` 并在 download 决策中直接给出 +`activate` 布尔——三端编排器各自只读这一个字段,零判断逻辑。 + +刻意的语义边界: +- **仅作用于原生**。JS 侧交互策略不感知不受影响——健康设备该弹窗还弹窗, + 用户点"取消"只是"这次不切",下次冷启动仍会进入标记版本(原生分不出砖机 + 与健康设备,这正是显式标记版本想要的触达)。 +- **本机 `rolledBack` 黑名单赢过 forceBoot**(守卫在谓词之前):本机有崩溃 + 证据的版本不会被重装,开发者应重绑到别的版本。 +- **first_time 崩溃保护对强制版本依然生效**:强制启动的版本若也是坏的, + 下次启动照常回滚,不存在"强制进入坏版本且无法回头"。 + +服务端配合(pushy-server / cresc-server,另行实施):根版本响应透传 +`config`(现只有 expVersion 携带);控制台版本编辑与重绑界面加 forceBoot +开关。 diff --git a/android/lib/arm64-v8a/librnupdate.so b/android/lib/arm64-v8a/librnupdate.so index d830350e88727d5040fec32a9f3cb7fb7ed01745..c1930a6fb14eb021ec4a7805d1f9ced7331cd764 100755 GIT binary patch delta 69042 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 delta 68862 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 delta 22287 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 diff --git a/android/lib/x86/librnupdate.so b/android/lib/x86/librnupdate.so index 3a74eb11e21971a82e1d4a96df141712abbb52f3..34c1288ee750ce91508fafa23d790f7d9cd8cc2b 100755 GIT binary patch delta 42927 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 XiFfO%2{-XIx1Y`5knj#k-{JoNh83?D diff --git a/android/lib/x86_64/librnupdate.so b/android/lib/x86_64/librnupdate.so index d9d5b36e2e08eefe4fe8f375ee3e7b30f8da3d64..acac7414f1ae7e78c875a4bf95cace38c781b1a1 100755 GIT binary patch delta 65333 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 YdN&R@7cemNO@(i!s)(;0vw*t)51CBg4FCWD delta 63352 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 tried = new HashSet<>(); + int httpAttempts = 0; for (int i = 0; i < ordered.length(); i++) { - String base = ordered.optString(i, ""); + String base = normalizeEndpointBase(ordered.optString(i, "")); if (base.isEmpty()) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return null; + } tried.add(base); String response = httpRequest(base + "/checkUpdate/" + appKey, body); if (isValidCheckResponse(response)) { @@ -289,6 +308,9 @@ private static String runCheckRequest(JSONObject config, String appKey, String b if (listUrl.isEmpty()) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return null; + } String listText = httpRequest(listUrl, null); if (listText == null) { continue; @@ -300,10 +322,13 @@ private static String runCheckRequest(JSONObject config, String appKey, String b continue; } for (int j = 0; j < remote.length(); j++) { - String base = remote.optString(j, ""); + String base = normalizeEndpointBase(remote.optString(j, "")); if (base.isEmpty() || tried.contains(base)) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return null; + } tried.add(base); String response = httpRequest(base + "/checkUpdate/" + appKey, body); if (isValidCheckResponse(response)) { @@ -322,6 +347,8 @@ private static boolean performAttempts( if (attempts == null) { return false; } + final long deadlineNanos = System.nanoTime() + + TimeUnit.SECONDS.toNanos(DOWNLOAD_ROUND_TIMEOUT_SECONDS); for (int i = 0; i < attempts.length(); i++) { JSONObject attempt = attempts.optJSONObject(i); if (attempt == null) { @@ -367,11 +394,13 @@ public void onDownloadFailed(Throwable error) { context.downloadFullUpdate(url, hash, listener); } try { - // Backstop only — the download pipeline carries its own - // timeouts/failures. - if (!latch.await(600, TimeUnit.SECONDS)) { + // One deadline covers the complete diff→pdiff→full round; + // each DownloadTask also has its own per-call timeout. + long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0 + || !latch.await(remainingNanos, TimeUnit.NANOSECONDS)) { Log.w(UpdateContext.TAG, - "native check: " + type + " attempt timed out"); + "native check: download round timed out during " + type); return false; } } catch (InterruptedException e) { diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java index 64e7b613..5bd0849c 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java @@ -266,7 +266,7 @@ public static void syncNativeConfig( StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "syncNativeConfig", new StateSerialRunner.Operation() { @Override public void run() { - updateContext.setKv("nativeConfig", config); + updateContext.setKv(NativeCheckOrchestrator.KEY_CONFIG, config); promise.resolve(true); } }); diff --git a/cpp/update_flow_core/flow_json.cpp b/cpp/update_flow_core/flow_json.cpp index dfa90668..471a2d7c 100644 --- a/cpp/update_flow_core/flow_json.cpp +++ b/cpp/update_flow_core/flow_json.cpp @@ -445,19 +445,57 @@ class Parser { if (pos_ < text_.size() && text_[pos_] == '-') { pos_++; } - while (pos_ < text_.size()) { - char c = text_[pos_]; - if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || - c == '+' || c == '-') { + + if (pos_ >= text_.size()) { + ok_ = false; + return Value::Undefined(); + } + if (text_[pos_] == '0') { + pos_++; + // RFC 8259 forbids leading zeroes (except the number zero itself). + if (pos_ < text_.size() && text_[pos_] >= '0' && text_[pos_] <= '9') { + ok_ = false; + return Value::Undefined(); + } + } else if (text_[pos_] >= '1' && text_[pos_] <= '9') { + while (pos_ < text_.size() && text_[pos_] >= '0' && + text_[pos_] <= '9') { pos_++; - } else { - break; } - } - if (pos_ == start) { + } else { ok_ = false; return Value::Undefined(); } + + if (pos_ < text_.size() && text_[pos_] == '.') { + pos_++; + if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') { + ok_ = false; + return Value::Undefined(); + } + while (pos_ < text_.size() && text_[pos_] >= '0' && + text_[pos_] <= '9') { + pos_++; + } + } + + if (pos_ < text_.size() && + (text_[pos_] == 'e' || text_[pos_] == 'E')) { + pos_++; + if (pos_ < text_.size() && + (text_[pos_] == '+' || text_[pos_] == '-')) { + pos_++; + } + if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') { + ok_ = false; + return Value::Undefined(); + } + while (pos_ < text_.size() && text_[pos_] >= '0' && + text_[pos_] <= '9') { + pos_++; + } + } + char* end = nullptr; std::string token = text_.substr(start, pos_ - start); double n = std::strtod(token.c_str(), &end); diff --git a/cpp/update_flow_core/tests/flow_vectors.json b/cpp/update_flow_core/tests/flow_vectors.json index 5c01f695..2290f932 100644 --- a/cpp/update_flow_core/tests/flow_vectors.json +++ b/cpp/update_flow_core/tests/flow_vectors.json @@ -907,7 +907,15 @@ } ], "expected": { - "upToDate": true + "update": true, + "name": "gray", + "description": "d", + "metaInfo": "m", + "config": { + "rollout": { + "2.3.4": 63 + } + } } }, { @@ -1191,10 +1199,8 @@ false ], "expected": { - "action": "download", - "hash": "next-hash", - "attempts": [], - "devNoop": false + "action": "none", + "reason": "noArtifact" } }, { @@ -1215,10 +1221,8 @@ false ], "expected": { - "action": "download", - "hash": "next-hash", - "attempts": [], - "devNoop": false + "action": "none", + "reason": "noArtifact" } }, { @@ -1236,10 +1240,8 @@ false ], "expected": { - "action": "download", - "hash": "next-hash", - "attempts": [], - "devNoop": false + "action": "none", + "reason": "noArtifact" } }, { diff --git a/cpp/update_flow_core/tests/update_flow_core_test.cpp b/cpp/update_flow_core/tests/update_flow_core_test.cpp index a6117dde..cebab9a9 100644 --- a/cpp/update_flow_core/tests/update_flow_core_test.cpp +++ b/cpp/update_flow_core/tests/update_flow_core_test.cpp @@ -72,6 +72,11 @@ int RunParserRobustness() { "1x", "1 2", "- 1", + "01", + "-01", + "1.", + "1e", + "1e+", "\"\\q\"", "\"\\u12\"", "\"\\ud800\\u0041\"", // high surrogate followed by a non-low escape @@ -274,6 +279,10 @@ int main(int argc, char* argv[]) { } const Value& cases = doc.Get("cases"); + if (!cases.IsArray() || cases.Size() == 0) { + std::fprintf(stderr, "%s must contain a non-empty cases array\n", argv[1]); + return 2; + } int failures = RunParserRobustness() + RunHandleCheckResponse(); for (size_t i = 0; i < cases.Size(); i++) { const Value& testCase = cases.At(i); diff --git a/cpp/update_flow_core/update_flow_core.cpp b/cpp/update_flow_core/update_flow_core.cpp index d55d77c7..3bfb1f0d 100644 --- a/cpp/update_flow_core/update_flow_core.cpp +++ b/cpp/update_flow_core/update_flow_core.cpp @@ -127,11 +127,15 @@ Value OrderEndpointCandidates(const Value& endpoints, double randomSample) { if (n < 2) { return deduped; } - double idx = std::floor(randomSample * static_cast(n)); - size_t first = idx <= 0 ? 0 - : (idx >= static_cast(n - 1) - ? n - 1 - : static_cast(idx)); + const double idx = std::isfinite(randomSample) + ? std::floor(randomSample * static_cast(n)) + : 0; + // Validate in floating-point space before converting to size_t: converting + // NaN, infinity, or an out-of-range value is undefined behavior in C++. + const size_t first = idx <= 0 ? 0 + : (idx >= static_cast(n - 1) + ? n - 1 + : static_cast(idx)); Value ordered = Value::Array(); ordered.Push(deduped.At(first)); for (size_t i = 0; i < n; i++) { @@ -184,7 +188,9 @@ Value ResolveCheckResult(const Value& rootInfo, const Value& identity) { if (rootResult.Get("update").Truthy() && expVersion.Truthy() && rollout.IsNumber()) { if (IsInRollout(rollout.AsNumber(), identity.Get("uuid").AsString())) { - if (Value::StrictEquals(expVersion.Get("hash"), currentVersion)) { + const Value& expHash = expVersion.Get("hash"); + if (expHash.IsString() && !expHash.AsString().empty() && + Value::StrictEquals(expHash, currentVersion)) { Value upToDate = Value::Object(); upToDate.Set("upToDate", Value::Bool(true)); return upToDate; @@ -200,8 +206,10 @@ Value ResolveCheckResult(const Value& rootInfo, const Value& identity) { return info; } } - if (rootResult.Get("update").Truthy() && - Value::StrictEquals(rootResult.Get("hash"), currentVersion)) { + const Value& rootHash = rootResult.Get("hash"); + if (rootResult.Get("update").Truthy() && rootHash.IsString() && + !rootHash.AsString().empty() && + Value::StrictEquals(rootHash, currentVersion)) { Value upToDate = Value::Object(); upToDate.Set("upToDate", Value::Bool(true)); return upToDate; @@ -252,12 +260,17 @@ Value DecideDownload(const Value& info, const Value& identity, bool isDev) { Value fullUrls = JoinUrls(paths, info.Get("full")); pushAttempt("full", fullUrls); + const bool devNoop = + isDev && !(fullUrls.IsArray() && fullUrls.Size() > 0); + if (attempts.Size() == 0 && !devNoop) { + return DeclineDownload("noArtifact"); + } + Value decision = Value::Object(); decision.Set("action", Value::String("download")); decision.Set("hash", hash); decision.Set("attempts", std::move(attempts)); - decision.Set("devNoop", - Value::Bool(isDev && !(fullUrls.IsArray() && fullUrls.Size() > 0))); + decision.Set("devNoop", Value::Bool(devNoop)); return decision; } diff --git a/cpp/update_flow_core/update_flow_jni.cpp b/cpp/update_flow_core/update_flow_jni.cpp index f22aa3b4..f6397774 100644 --- a/cpp/update_flow_core/update_flow_jni.cpp +++ b/cpp/update_flow_core/update_flow_jni.cpp @@ -4,7 +4,10 @@ // "input did not parse" and the caller skips the check round. #include +#include +#include #include +#include #include "../patch_core/jni_util.h" #include "flow_json.h" @@ -13,7 +16,64 @@ namespace { jstring ToJString(JNIEnv* env, const std::string& value) { - return env->NewStringUTF(value.c_str()); + // flow_json emits standard UTF-8, while NewStringUTF expects JNI's modified + // UTF-8 and corrupts supplementary code points. Decode explicitly and pass + // UTF-16 code units to NewString. Accept encoded surrogate code points too: + // strings originating in Java may have entered through modified UTF-8. + std::vector utf16; + utf16.reserve(value.size()); + size_t i = 0; + while (i < value.size()) { + const unsigned char first = static_cast(value[i]); + uint32_t codePoint = 0xfffd; + size_t width = 1; + if (first < 0x80) { + codePoint = first; + } else if (first >= 0xc2 && first <= 0xdf && i + 1 < value.size()) { + const unsigned char b1 = static_cast(value[i + 1]); + if ((b1 & 0xc0) == 0x80) { + codePoint = ((first & 0x1f) << 6) | (b1 & 0x3f); + width = 2; + } + } else if (first == 0xc0 && i + 1 < value.size() && + static_cast(value[i + 1]) == 0x80) { + codePoint = 0; // modified UTF-8 encoding of U+0000 + width = 2; + } else if (first >= 0xe0 && first <= 0xef && i + 2 < value.size()) { + const unsigned char b1 = static_cast(value[i + 1]); + const unsigned char b2 = static_cast(value[i + 2]); + if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 && + !(first == 0xe0 && b1 < 0xa0)) { + codePoint = ((first & 0x0f) << 12) | ((b1 & 0x3f) << 6) | + (b2 & 0x3f); + width = 3; + } + } else if (first >= 0xf0 && first <= 0xf4 && i + 3 < value.size()) { + const unsigned char b1 = static_cast(value[i + 1]); + const unsigned char b2 = static_cast(value[i + 2]); + const unsigned char b3 = static_cast(value[i + 3]); + if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 && + (b3 & 0xc0) == 0x80 && !(first == 0xf0 && b1 < 0x90) && + !(first == 0xf4 && b1 > 0x8f)) { + codePoint = ((first & 0x07) << 18) | ((b1 & 0x3f) << 12) | + ((b2 & 0x3f) << 6) | (b3 & 0x3f); + width = 4; + } + } + i += width; + if (codePoint <= 0xffff) { + utf16.push_back(static_cast(codePoint)); + } else { + codePoint -= 0x10000; + utf16.push_back(static_cast(0xd800 + (codePoint >> 10))); + utf16.push_back(static_cast(0xdc00 + (codePoint & 0x3ff))); + } + } + if (utf16.size() > static_cast(std::numeric_limits::max())) { + return nullptr; + } + return env->NewString(utf16.empty() ? nullptr : utf16.data(), + static_cast(utf16.size())); } } // namespace diff --git a/harmony/pushy/src/main/ets/DownloadTask.ts b/harmony/pushy/src/main/ets/DownloadTask.ts index 0740a088..06ddc627 100644 --- a/harmony/pushy/src/main/ets/DownloadTask.ts +++ b/harmony/pushy/src/main/ets/DownloadTask.ts @@ -12,7 +12,7 @@ import NativePatchCore, { CopyGroupResult, } from './NativePatchCore'; -const VERSION_COMPLETE_FILE_NAME = '.pushy-complete'; +export const VERSION_COMPLETE_FILE_NAME = '.pushy-complete'; export interface PatchManifestArrays { copyFroms: string[]; diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index af756605..1545949e 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -3,6 +3,7 @@ import deviceInfo from '@ohos.deviceInfo'; import logger from './Logger'; import NativePatchCore from './NativePatchCore'; import type { UpdateContext } from './UpdateContext'; +import { isSafePathComponent } from './PathUtils'; // 原生冷启动检测(NATIVE_CHECKUPDATE_DESIGN §10):每进程一次,getBundleUrl // 后延迟数秒运行,完全不依赖 app bundle——坏热更把 JS 砸挂后,下次启动仍能 @@ -12,16 +13,18 @@ import type { UpdateContext } from './UpdateContext'; // 鸿蒙的 debug 门控由触发点天然承担:dev 走 MetroJSBundleProvider, // PushyFileJSBundleProvider.getBundleUrl 不会被调用。 const TAG = 'NativeCheck'; -const KEY_CONFIG = 'nativeConfig'; +export const KEY_CONFIG = 'nativeConfig'; // 供 JS 侧复用的原始响应缓存(§10.3),同时记录请求与配置指纹以限定命中范围。 -const KEY_RESP_CACHE = 'nativeCheckResp'; +export const KEY_RESP_CACHE = 'nativeCheckResp'; const REQUEST_TIMEOUT_MS = 10000; +const MAX_CHECK_HTTP_ATTEMPTS = 8; const START_DELAY_MS = 5000; const DOWNLOAD_TYPE_DIFF = 'diff'; const DOWNLOAD_TYPE_PDIFF = 'pdiff'; interface NativeConfig { appKey?: string; + packageVersion?: string; endpoints?: string[]; queryUrls?: string[]; afterDownload?: string; @@ -127,9 +130,10 @@ async function runOnce( // the launch-path snapshot captured before that happens. const rolledBackVersion = launchRolledBackVersion; const uuid = context.getKv('uuid') ?? ''; + const packageVersion = config.packageVersion || context.getPackageVersion(); const identity: FlowIdentity = { - packageVersion: context.getPackageVersion(), + packageVersion, currentVersion, uuid, }; @@ -140,7 +144,9 @@ async function runOnce( const cInfo: FlowCInfo = { rnu: config.rnu ?? '', rn: config.rn ?? '', - os: `harmony ${deviceInfo.sdkApiVersion}`, + // RNOH's Platform.Version is osFullName; use the same value so JS can + // reuse the response cache produced by this native request. + os: `harmony ${deviceInfo.osFullName}`, uuid, }; @@ -178,7 +184,7 @@ async function runOnce( return; } const hash = decision.hash ?? ''; - if (!hash) { + if (!isSafePathComponent(hash)) { return; } @@ -307,10 +313,15 @@ async function runCheckRequest( } const ordered = JSON.parse(orderedJson) as string[]; const tried = new Set(); - for (const base of ordered) { + let httpAttempts = 0; + for (const rawBase of ordered) { + const base = normalizeEndpointBase(rawBase); if (!base) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return undefined; + } tried.add(base); const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); if (isValidCheckResponse(response)) { @@ -321,6 +332,9 @@ async function runCheckRequest( if (!listUrl) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return undefined; + } const listText = await httpRequest(listUrl); if (listText === undefined) { continue; @@ -335,10 +349,17 @@ async function runCheckRequest( } catch (e) { continue; } - for (const base of remote) { - if (typeof base !== 'string' || !base || tried.has(base)) { + for (const rawBase of remote) { + if (typeof rawBase !== 'string') { + continue; + } + const base = normalizeEndpointBase(rawBase); + if (!base || tried.has(base)) { continue; } + if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { + return undefined; + } tried.add(base); const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); if (isValidCheckResponse(response)) { @@ -351,6 +372,10 @@ async function runCheckRequest( return undefined; } +function normalizeEndpointBase(base: string): string { + return base.replace(/\/+$/, ''); +} + async function performAttempts( context: UpdateContext, attempts: DecisionAttempt[], diff --git a/harmony/pushy/src/main/ets/PathUtils.ts b/harmony/pushy/src/main/ets/PathUtils.ts new file mode 100644 index 00000000..383f1d81 --- /dev/null +++ b/harmony/pushy/src/main/ets/PathUtils.ts @@ -0,0 +1,21 @@ +// Server-controlled identifiers are used as children of the update root. Keep +// validation in a dependency-free module so startup orchestration and storage +// code can share it without creating an import cycle. +export function isSafePathComponent(name: string): boolean { + return ( + typeof name === 'string' && + name.length > 0 && + name !== '.' && + name !== '..' && + !name.includes('/') && + !name.includes('\\') && + !name.includes('\0') + ); +} + +export function assertSafePathComponent(name: string): string { + if (!isSafePathComponent(name)) { + throw Error(`Invalid path component: ${name}`); + } + return name; +} diff --git a/harmony/pushy/src/main/ets/PushyTurboModule.ts b/harmony/pushy/src/main/ets/PushyTurboModule.ts index 9109b5a5..f9a91691 100644 --- a/harmony/pushy/src/main/ets/PushyTurboModule.ts +++ b/harmony/pushy/src/main/ets/PushyTurboModule.ts @@ -8,6 +8,10 @@ import logger from './Logger'; import NativePatchCore from './NativePatchCore'; import { UpdateContext } from './UpdateContext'; import { EventHub } from './EventHub'; +import { + KEY_CONFIG, + KEY_RESP_CACHE, +} from './NativeCheckOrchestrator'; const TAG = 'PushyTurboModule'; @@ -194,14 +198,14 @@ export class PushyTurboModule extends UITurboModule { async syncNativeConfig(config: string): Promise { logger.debug(TAG, ',call syncNativeConfig'); JSON.parse(config); - this.context.setKv('nativeConfig', config); + this.context.setKv(KEY_CONFIG, config); } // 原生冷启动检测落盘的原始响应缓存,JS 侧新鲜期内直接复用免二次请求 // (NATIVE_CHECKUPDATE_DESIGN §10.3)。缺省空串,永不 reject。 async getNativeCheckCache(): Promise { logger.debug(TAG, ',call getNativeCheckCache'); - return this.context.getKv('nativeCheckResp') ?? ''; + return this.context.getKv(KEY_RESP_CACHE) ?? ''; } async reloadUpdate(options: { hash: string }): Promise { diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index 0c6fad20..c3fec8e0 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -1,6 +1,9 @@ import preferences from '@ohos.data.preferences'; import fileIo from '@ohos.file.fs'; -import { DownloadTask } from './DownloadTask'; +import { + DownloadTask, + VERSION_COMPLETE_FILE_NAME, +} from './DownloadTask'; import common from '@ohos.app.ability.common'; import { DownloadTaskParams } from './DownloadTaskParams'; import { bundleManager } from '@kit.AbilityKit'; @@ -16,32 +19,14 @@ import NativePatchCore, { STATE_OP_SWITCH_VERSION, StateCoreResult, } from './NativePatchCore'; +import { assertSafePathComponent } from './PathUtils'; + +export { isSafePathComponent } from './PathUtils'; type FlushablePreferences = preferences.Preferences & { flushSync?: () => void; }; -// 服务端下发的 hash/originHash/fileName 会拼进 rootDir 作为子路径;凡是可能 -// 逃出 rootDir 的值(路径分隔符、".."、".")必须在触碰文件系统前拒绝。 -export function isSafePathComponent(name: string): boolean { - return ( - typeof name === 'string' && - name.length > 0 && - name !== '.' && - name !== '..' && - !name.includes('/') && - !name.includes('\\') && - !name.includes('\0') - ); -} - -function assertSafePathComponent(name: string): string { - if (!isSafePathComponent(name)) { - throw Error(`Invalid path component: ${name}`); - } - return name; -} - export class UpdateContext { private context: common.UIAbilityContext; private rootDir: string; @@ -542,7 +527,9 @@ export class UpdateContext { try { const safeHash = assertSafePathComponent(hash); return fileIo.accessSync(this.getBundlePath(safeHash)) - && fileIo.accessSync(`${this.rootDir}/${safeHash}/.pushy-complete`); + && fileIo.accessSync( + `${this.rootDir}/${safeHash}/${VERSION_COMPLETE_FILE_NAME}`, + ); } catch (e) { return false; } diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index f4178cff..c7d46664 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -1258,19 +1258,36 @@ + (NSString *)buildTime } dispatch_semaphore_t sem = dispatch_semaphore_create(0); __block NSString *result = nil; - [[[NSURLSession sharedSession] dataTaskWithRequest:request + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSInteger status = [(NSHTTPURLResponse *)response statusCode]; - if (error == nil && status >= 200 && status < 300 && data != nil) { + NSHTTPURLResponse *httpResponse = + [response isKindOfClass:[NSHTTPURLResponse class]] + ? (NSHTTPURLResponse *)response + : nil; + NSInteger status = httpResponse.statusCode; + if (error == nil && httpResponse != nil && status >= 200 && + status < 300 && data != nil) { result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } dispatch_semaphore_signal(sem); - }] resume]; - dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, - (int64_t)((timeout + 5) * NSEC_PER_SEC))); + }]; + [task resume]; + if (dispatch_semaphore_wait( + sem, dispatch_time(DISPATCH_TIME_NOW, + (int64_t)((timeout + 5) * NSEC_PER_SEC))) != 0) { + [task cancel]; + return nil; + } return result; } +static NSString *PushyNormalizeEndpointBase(NSString *base) { + while ([base hasSuffix:@"/"]) { + base = [base substringToIndex:base.length - 1]; + } + return base; +} + static BOOL PushyIsValidCheckResponse(NSString *responseText) { if (responseText == nil) { return NO; @@ -1330,6 +1347,11 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { if (appKey.length == 0) { return; } + NSString *packageVersion = + PushyFromStdString(config.Get("packageVersion").AsString()); + if (packageVersion.length == 0) { + packageVersion = [RCTPushy packageVersion]; + } __block NSString *currentVersion = nil; PushyWithStateLock(^{ @@ -1341,7 +1363,7 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { flowjson::Value identity = flowjson::Value::Object(); identity.Set("packageVersion", - flowjson::Value::String(PushyToStdString([RCTPushy packageVersion]))); + flowjson::Value::String(PushyToStdString(packageVersion))); if (currentVersion != nil) { identity.Set("currentVersion", flowjson::Value::String(PushyToStdString(currentVersion))); @@ -1467,11 +1489,17 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config flowjson::Value ordered = updateflow::OrderEndpointCandidates(config.Get("endpoints"), sample); NSMutableSet *tried = [NSMutableSet set]; + const NSUInteger maxHttpAttempts = 8; + NSUInteger httpAttempts = 0; for (const auto &endpoint : ordered.elements()) { - NSString *base = PushyFromStdString(endpoint.AsString()); - if (base == nil) { + NSString *base = PushyNormalizeEndpointBase( + PushyFromStdString(endpoint.AsString())); + if (base.length == 0) { continue; } + if (httpAttempts++ >= maxHttpAttempts) { + return nil; + } [tried addObject:base]; NSString *response = PushyHttpRequest( [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], @@ -1485,6 +1513,9 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config if (listUrl == nil) { continue; } + if (httpAttempts++ >= maxHttpAttempts) { + return nil; + } NSString *listText = PushyHttpRequest(listUrl, @"GET", nil, 10); if (listText == nil) { continue; @@ -1495,10 +1526,14 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config continue; } for (const auto &endpoint : remote.elements()) { - NSString *base = PushyFromStdString(endpoint.AsString()); - if (base == nil || [tried containsObject:base]) { + NSString *base = PushyNormalizeEndpointBase( + PushyFromStdString(endpoint.AsString())); + if (base.length == 0 || [tried containsObject:base]) { continue; } + if (httpAttempts++ >= maxHttpAttempts) { + return nil; + } [tried addObject:base]; NSString *response = PushyHttpRequest( [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], diff --git a/scripts/build-harmony-har.js b/scripts/build-harmony-har.js index 1650daeb..28f4fc9d 100644 --- a/scripts/build-harmony-har.js +++ b/scripts/build-harmony-har.js @@ -216,6 +216,12 @@ function syncHarmonyNativeSources() { path.join(patchCoreDir, 'patch_core.cpp'), )}`, ); + ensureFileExists( + path.join(updateFlowCoreDir, 'update_flow_core.cpp'), + `Missing shared update flow core source: ${relativeToProject( + path.join(updateFlowCoreDir, 'update_flow_core.cpp'), + )}`, + ); fs.rmSync(harmonyNativeStageDir, { recursive: true, force: true }); fs.mkdirSync(path.join(harmonyNativeStageJniDir, 'lzma'), { diff --git a/scripts/generate-flow-vectors.ts b/scripts/generate-flow-vectors.ts index 7e1f7556..9fea934f 100644 --- a/scripts/generate-flow-vectors.ts +++ b/scripts/generate-flow-vectors.ts @@ -12,6 +12,8 @@ // omitting `expected`, and undefined-valued object members disappear on // serialization — the C++ side mirrors both (Kind::Undefined members are // skipped by stringify). + +import { fileURLToPath } from 'node:url'; import { buildCheckRequestBody, decideDownload, @@ -270,17 +272,19 @@ export const buildVectors = (): FlowVector[] => { 'none' ); add('shouldActivateAfterDownload', { hash: 'x', config: {} }, 'none'); - add('shouldActivateAfterDownload', { hash: 'x', config: { forceBoot: true } }); + add('shouldActivateAfterDownload', { + hash: 'x', + config: { forceBoot: true }, + }); add('shouldActivateAfterDownload', { upToDate: true }, 'none'); return cases; }; if (import.meta.main) { - const outPath = new URL( - '../cpp/update_flow_core/tests/flow_vectors.json', - import.meta.url - ).pathname; + const outPath = fileURLToPath( + new URL('../cpp/update_flow_core/tests/flow_vectors.json', import.meta.url) + ); const doc = { generated_by: 'scripts/generate-flow-vectors.ts — do not edit by hand', cases: buildVectors(), diff --git a/scripts/test-update-flow-core.sh b/scripts/test-update-flow-core.sh index 47c955c3..30187cef 100755 --- a/scripts/test-update-flow-core.sh +++ b/scripts/test-update-flow-core.sh @@ -1,6 +1,8 @@ #!/bin/sh set -eu +# Intentional one-command CDPATH assignment. +# shellcheck disable=SC1007 ROOT_DIR="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)" BUILD_DIR="$ROOT_DIR/.tmp/update-flow-core-tests" @@ -14,6 +16,8 @@ if [ "${SANITIZE:-0}" = "1" ]; then echo "Building update flow core tests with AddressSanitizer + UBSan" fi +# SANITIZE_FLAGS must split into compiler arguments. +# shellcheck disable=SC2086 c++ \ -std=c++17 \ -Wall \ diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 6e8671f3..5894e5a5 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -1382,6 +1382,7 @@ describe('syncNativeConfig', () => { expect(config.queryUrls.length).toBeGreaterThan(0); expect(config.rnu).toBe('10.0.0'); expect(config.rn).toBe('0.73.0'); + expect(config.packageVersion).toBe('1.0.0'); }); test('alert strategies keep activation with JS (afterDownload none)', async () => { @@ -1402,6 +1403,9 @@ describe('syncNativeConfig', () => { const { Pushy } = await importFreshClient('sync-config-3'); const client = new Pushy({ appKey: 'demo-app' }); client.setOptions({ updateStrategy: 'silentAndNow' }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); const config = JSON.parse( (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] @@ -1409,6 +1413,83 @@ describe('syncNativeConfig', () => { expect(config.afterDownload).toBe('setNeedUpdate'); }); + test('persists and exposes the effective overridden package version', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-package-override'); + const client = new Pushy({ + appKey: 'demo-app', + overridePackageVersion: '9.9.9', + }); + + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.packageVersion).toBe('9.9.9'); + expect(client.getEffectivePackageVersion()).toBe('9.9.9'); + }); + + test('skips duplicate config writes after the same value succeeds', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-dedupe'); + const client = new Pushy({ appKey: 'demo-app' }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + client.setOptions({ appKey: 'demo-app' }); + + expect(syncNativeConfig).toHaveBeenCalledTimes(1); + }); + + test('serializes config writes and preserves the newest pending value', async () => { + const resolvers: Array<() => void> = []; + const syncNativeConfig = mock( + () => + new Promise((resolve) => { + resolvers.push(resolve); + }) + ); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-serialized'); + const client = new Pushy({ appKey: 'demo-app' }); + + client.setOptions({ updateStrategy: 'silentAndNow' }); + expect(syncNativeConfig).toHaveBeenCalledTimes(1); + resolvers[0](); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + expect(syncNativeConfig).toHaveBeenCalledTimes(2); + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.afterDownload).toBe('setNeedUpdate'); + resolvers[1](); + }); + + test('retries a config write that failed', async () => { + let calls = 0; + const syncNativeConfig = mock(() => { + calls++; + return calls === 1 + ? Promise.reject(new Error('write failed')) + : Promise.resolve(); + }); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-retry'); + const client = new Pushy({ appKey: 'demo-app' }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + client.setOptions({ appKey: 'demo-app' }); + + expect(syncNativeConfig).toHaveBeenCalledTimes(2); + }); + test('an older native module without the method is skipped silently', async () => { setupClientMocks(); const { Pushy } = await importFreshClient('sync-config-4'); diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index cccdb5af..d49433ff 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -34,6 +34,7 @@ const createClient = (options: Record = {}) => { autoMarkSuccess: false, ...options, }, + getEffectivePackageVersion: () => options.overridePackageVersion || '1.0.0', assertDebug: () => true, checkUpdate: mock( async (): Promise => ({ ...updateResult }) diff --git a/src/__tests__/resolveCheckResult.test.ts b/src/__tests__/resolveCheckResult.test.ts index 208c963a..7eacf789 100644 --- a/src/__tests__/resolveCheckResult.test.ts +++ b/src/__tests__/resolveCheckResult.test.ts @@ -128,4 +128,38 @@ describe('resolveCheckResult', () => { expect(result).toEqual(createRootResult()); }); + + test('does not treat two missing hashes as already current', () => { + const result = resolveCheckResult( + createRootResult({ + expVersion: { + name: 'gray-without-hash', + config: { + rollout: { + [identity.packageVersion]: 100, + }, + }, + } as any, + }), + { ...identity, currentVersion: undefined } + ); + + expect(result).toEqual({ + update: true, + name: 'gray-without-hash', + config: { + rollout: { + [identity.packageVersion]: 100, + }, + }, + paths: ['cdn.example.com'], + }); + + expect( + resolveCheckResult( + { update: true }, + { ...identity, currentVersion: undefined } + ) + ).toEqual({ update: true }); + }); }); diff --git a/src/__tests__/updateFlowCore.test.ts b/src/__tests__/updateFlowCore.test.ts index d358b9e3..3b458329 100644 --- a/src/__tests__/updateFlowCore.test.ts +++ b/src/__tests__/updateFlowCore.test.ts @@ -112,6 +112,15 @@ describe('orderEndpointCandidates', () => { expect(orderEndpointCandidates([], 0.5)).toEqual([]); expect(orderEndpointCandidates(['a'], 0.5)).toEqual(['a']); }); + + test('clamps invalid or out-of-range samples before indexing', () => { + expect(orderEndpointCandidates(['a', 'b'], Number.NaN)).toEqual(['a', 'b']); + expect( + orderEndpointCandidates(['a', 'b'], Number.POSITIVE_INFINITY) + ).toEqual(['a', 'b']); + expect(orderEndpointCandidates(['a', 'b'], -1)).toEqual(['a', 'b']); + expect(orderEndpointCandidates(['a', 'b'], 2)).toEqual(['b', 'a']); + }); }); describe('decideDownload', () => { @@ -194,13 +203,11 @@ describe('decideDownload', () => { expect(decision.attempts.map((a) => a.type)).toEqual(['pdiff', 'full']); }); - test('yields no attempts when paths are empty', () => { - const decision = decideDownload(updateInfo({ paths: [] }), identity); - if (decision.action !== 'download') { - throw new Error('expected a download decision'); - } - expect(decision.attempts).toEqual([]); - expect(decision.devNoop).toBe(false); + test('declines a release update when no artifact URL can be built', () => { + expect(decideDownload(updateInfo({ paths: [] }), identity)).toEqual({ + action: 'none', + reason: 'noArtifact', + }); }); test('dev only attempts full', () => { diff --git a/src/client.ts b/src/client.ts index 3632b01e..1921b2d3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -166,6 +166,9 @@ export class Pushy { // Endpoint that most recently served a successful checkUpdate; telemetry // reuses it instead of re-running the fallback race. private lastWorkingEndpoint?: string; + private syncedNativeConfigJson?: string; + private pendingNativeConfigJson?: string; + private nativeConfigSyncInFlight = false; version = cInfo.rnu; loggerPromise = (() => { @@ -283,6 +286,7 @@ export class Pushy { } return JSON.stringify({ appKey, + packageVersion: this.getEffectivePackageVersion(), endpoints: server.main, queryUrls: server.queryUrls ?? [], // The native check may activate a downloaded version (next launch) @@ -297,18 +301,53 @@ export class Pushy { }); }; + private flushNativeConfig = () => { + if (this.nativeConfigSyncInFlight) { + return; + } + const configJson = this.pendingNativeConfigJson; + this.pendingNativeConfigJson = undefined; + if (!configJson || configJson === this.syncedNativeConfigJson) { + return; + } + this.nativeConfigSyncInFlight = true; + let syncResult: Promise; + try { + syncResult = Promise.resolve(PushyModule.syncNativeConfig(configJson)); + } catch (e: any) { + this.nativeConfigSyncInFlight = false; + log('syncNativeConfig failed:', e?.message || e); + this.flushNativeConfig(); + return; + } + syncResult + .then(() => { + this.syncedNativeConfigJson = configJson; + }) + .catch((e: any) => { + log('syncNativeConfig failed:', e?.message || e); + }) + .finally(() => { + this.nativeConfigSyncInFlight = false; + this.flushNativeConfig(); + }); + }; + private syncNativeConfig = () => { const configJson = this.getNativeConfigJson(); - if (!configJson) { + if (!configJson || configJson === this.syncedNativeConfigJson) { return; } - Promise.resolve(PushyModule.syncNativeConfig(configJson)).catch( - (e: any) => { - log('syncNativeConfig failed:', e?.message || e); - } - ); + // Coalesce rapid setOptions calls, but serialize bridge writes so an older + // completion can never overwrite the newest desired configuration. + this.pendingNativeConfigJson = configJson; + this.flushNativeConfig(); }; + /** Package version used by every server-side update decision. */ + getEffectivePackageVersion = () => + this.options.overridePackageVersion || packageVersion; + private jsonStringsEqual = (left: string, right: string): boolean => { const compare = (a: unknown, b: unknown): boolean => { if (a === b) { @@ -475,8 +514,7 @@ export class Pushy { body: JSON.stringify({ type: payloadType, hash, - packageVersion: - this.options.overridePackageVersion || packageVersion, + packageVersion: this.getEffectivePackageVersion(), cInfo, detail: truncateDetail(detail), }), @@ -833,7 +871,7 @@ export class Pushy { } this.lastChecking = now; const fetchBody = buildCheckRequestBody({ - packageVersion: this.options.overridePackageVersion || packageVersion, + packageVersion: this.getEffectivePackageVersion(), currentVersion, buildTime, cInfo, diff --git a/src/provider.tsx b/src/provider.tsx index 24bea63f..145ddfa9 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -225,7 +225,11 @@ export const UpdateProvider = ({ } const info = resolveCheckResult( rootInfo, - { packageVersion, currentVersion, uuid: cInfo.uuid }, + { + packageVersion: client.getEffectivePackageVersion(), + currentVersion, + uuid: cInfo.uuid, + }, log ); if (info.update) { diff --git a/src/updateFlowCore.ts b/src/updateFlowCore.ts index de3adc6b..f8447d2c 100644 --- a/src/updateFlowCore.ts +++ b/src/updateFlowCore.ts @@ -132,9 +132,14 @@ export function orderEndpointCandidates( if (deduped.length < 2) { return deduped; } - const first = Math.min( - Math.floor(randomSample * deduped.length), - deduped.length - 1 + const first = Math.max( + 0, + Math.min( + Number.isFinite(randomSample) + ? Math.floor(randomSample * deduped.length) + : 0, + deduped.length - 1 + ) ); return [ deduped[first], @@ -200,7 +205,11 @@ export function resolveCheckResult( if (rootResult.update && expVersion && typeof rollout === 'number') { if (isInRollout(rollout, identity.uuid)) { log(`${expVersion.name} in ${rollout}% rollout, continue`); - if (expVersion.hash === identity.currentVersion) { + if ( + typeof expVersion.hash === 'string' && + expVersion.hash.length > 0 && + expVersion.hash === identity.currentVersion + ) { return { upToDate: true }; } const info: CheckResult = { @@ -214,7 +223,12 @@ export function resolveCheckResult( } log(`${expVersion.name} not in ${rollout}% rollout, ignored`); } - if (rootResult.update && rootResult.hash === identity.currentVersion) { + if ( + rootResult.update && + typeof rootResult.hash === 'string' && + rootResult.hash.length > 0 && + rootResult.hash === identity.currentVersion + ) { return { upToDate: true }; } return rootResult; @@ -255,7 +269,10 @@ export interface DownloadPlan { } export type DownloadDecision = - | { action: 'none'; reason: 'noUpdate' | 'alreadyCurrent' | 'rolledBack' } + | { + action: 'none'; + reason: 'noUpdate' | 'alreadyCurrent' | 'rolledBack' | 'noArtifact'; + } | ({ action: 'download' } & DownloadPlan); export function decideDownload( @@ -290,10 +307,14 @@ export function decideDownload( if (fullUrls?.length) { attempts.push({ type: 'full', urls: fullUrls }); } + const devNoop = !!isDev && !fullUrls?.length; + if (!attempts.length && !devNoop) { + return { action: 'none', reason: 'noArtifact' }; + } return { action: 'download', hash, attempts, - devNoop: !!isDev && !fullUrls?.length, + devNoop, }; } From 3229cd8df420eb98e91e761868e1b900d4462b46 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 10 Aug 2026 00:27:28 +0800 Subject: [PATCH 19/29] docs: record the open follow-ups from the native check review rounds Everything still open after two review rounds of the hardening branch, ordered by priority with current state, risk and suggested fixes: the Android/Harmony duplicate-download race (P1, the failure path can delete an installed version), the response-cache ts anchor and the per-platform deadline parity gap, the two semantic costs of iOS download joining, the .pushy-complete migration decision, the schedule-unreachable-on-throw path, two recorded-only P4 items, and the non-code release checklist (e2e, docs, 10.51.0, server-side activation steps). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 128 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 NATIVE_CHECK_FOLLOWUPS.md diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md new file mode 100644 index 00000000..f7af0c5d --- /dev/null +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -0,0 +1,128 @@ +# 原生冷启动检测:遗留改进项 + +> 来源:`agent/harden-native-check-update` 分支两轮评审 +> (f679e11 初次评审 10 项 → 181952a 修复 4 项、缓解 1 项)。 +> 本文只记**仍开放**的项;已修复项不再列。按优先级排序,每项含现状、 +> 风险与建议修法。发版(10.51.0)前建议至少处理 P1;P2 可随小版本; +> P3/P4 显式拍板"接受"也算关闭。 + +--- + +## P1 — Android/Harmony 重复下载竞态,失败路径可删除已安装版本 + +**现状**:iOS 在 f679e11 引入了进程级在途下载注册表 +(`PushyRegisterDownload`/`PushyFinishDownload`),同一 hash 的并发下载合流。 +Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已完成目录。 + +**风险**:JS 自动下载版本 H 进行中(单线程 executor 排队),原生检测 5 秒后 +`hasCompletedVersion(H)` 为 false,再排入第二个 H 的下载任务。JS 任务完成、 +写入 `.pushy-complete` 并 `setNeedUpdate(H)` 后,排队的重复任务重新下载 H +且中途失败(断网)时,`cleanUpAfterFailure` 删除 `rootDir/H`(含 +`index.bundlejs` 与标记)。下次冷启动解析到 H 但 bundle 缺失 → 回滚—— +**已成功安装的更新被静默丢失**。 + +**建议修法**(三选一,或组合): +1. 移植 iOS 的按 hash 在途注册表(Android 用 + `ConcurrentHashMap>`,Harmony 用模块级 Map); +2. 更小的止血:`cleanUpAfterFailure` 里若目标目录已有 + `.pushy-complete` 标记则不删(失败的是重复下载,不是这份安装); +3. DownloadTask 开跑前再查一次 `hasCompletedVersion(hash)`,已完成即直接 + 走成功回调。 +方案 2+3 组合改动最小且互补;方案 1 语义最完整(还能省一次重复下载)。 + +--- + +## P2 — 响应缓存的 `ts` 锚在持久化时刻,而非响应到达时刻 + +**现状**:`persistResponseCache` 在原生下载/激活全部结束后才落盘,`ts` 取 +当时时间。181952a 给 Android 加了整轮 600s deadline,把最坏偏移压到 +~12 分钟,但锚点本身未改;iOS 仍是逐 URL 600s、Harmony 依赖下载任务自身 +超时,无整轮上限。 + +**风险**:版本发布几分钟后被运营撤下/回滚时,一个"响应早已过时但 ts 很新" +的缓存会让 JS 在 120s 窗口内把已撤回的响应当新鲜结果复用,继续下载/激活 +已撤回版本。 + +**建议修法**:响应到达时捕获 `responseAtSeconds`,作为参数传入 +`persistResponseCache` 写进 `ts`(三端各一行改动,JS 读侧无需变);顺手把 +600s 整轮 deadline 补齐到 iOS/Harmony,消除三端 parity 缺口。 + +--- + +## P2 — iOS 合流下载的两个语义代价 + +**现状**:JS 的下载调用合流到冷启动引擎的在途下载后: +1. **收不到进度事件**——事件只由持有下载的实例发出,引擎实例 + `hasListeners=NO`,用户看到进度条冻在 0; +2. **继承异类 attempt 的失败**——引擎的 diff 尝试 404 时,JS 本可成功的 + full 下载调用被判失败(JS 策略链会继续下一策略+重试回退,最终多能成功, + 但单次调用的失败归因是错的)。 + +**建议修法**:注册表回调列表旁存进度回调,引擎下载的 NSURLSession 进度 +透传给所有 join 者(解决 1);join 时携带请求类型,类型不同不合流、改为 +排队串行(解决 2,代价是偶发的一次串行等待)。若认为 JS 策略链的自愈已 +足够,可显式拍板只修 1。 + +--- + +## P3 — `.pushy-complete` 无迁移,存量已下载版本升级后重下一次 + +**现状**:旧版本库下载的版本目录没有标记文件;升级到新库后 +`hasCompletedVersion` 判 false,原生检测整包重下一遍(下载完成后写标记, +自愈,每台受影响设备一次)。 + +**风险**:一次性流量成本,仅影响"已下载未激活"(alert 类策略)的存量设备。 + +**建议**:二选一显式拍板——(a) 接受成本,在 CHANGELOG 注明;(b) 加迁移: +首次运行时对"有 `index.bundlejs` 且 mtime 早于本次库安装"的目录补写标记 +(无法区分半解压目录,有误判风险,故 (a) 可能更稳)。 + +--- + +## P3 — 启动状态解析抛异常时,救援检测永不调度 + +**现状**:f679e11 把 `schedule` 从 `getBundleUrl`/`+bundleURL` 顶部移到 +各出口(为携带回滚快照),但若 `runStateCore`/回滚循环自身抛异常,所有 +出口都到不了。 + +**风险**:持久化状态损坏(恰是救援机制存在的场景)时,救援检测在后续所有 +启动中都不运行。 + +**建议修法**:状态解析段包 try/finally,finally 里以"空快照"调度 +(rolledBackVersion 传 null 即可,宁可少守卫不可不调度——与"判定失败不 +计数"同一取舍方向);三端同改。 + +--- + +## P4 — bundleHash 未就绪窗口的请求指纹失配(记录即可) + +**现状**:JS 预取 bundleHash 未 settle 时构造的请求体缺 `bundleHash` 键, +与原生缓存的请求(原生同步计算,总带该键)键集不同 → 指纹不命中,走网络。 +窗口极窄(通常仅首启,且该时刻缓存多半尚不存在);`overridePackageVersion` +的失配已在 181952a 通过配置携带 `packageVersion` 修复。 + +**建议**:在 `readNativeCheckCache` 注释里写明这是已知且有意的失配面, +不做代码改动。 + +--- + +## P4 — `readNativeCheckCache` 冗余解析(cleanup) + +**现状**:调用方刚 stringify 的请求体被立即 parse 回来做结构比较,外加对 +当前 config JSON 的一次重复解析——每次 release 路径 checkUpdate 的固定 +开销。 + +**建议**:把现成的 `fetchBody` 对象传入,只 parse `entry.request` 一侧。 + +--- + +## 发版清单(非代码缺陷,勿遗漏) + +- [ ] e2e:坏 bundle → 原生拉修复版 → 下次启动复活的端到端用例 + (沿用 Example/e2etest 基建;harmony 走 hdc/uitest 链路) +- [ ] README / README-CN / CHANGELOG:原生冷启动检测、forceBoot、 + 绑定依赖硬校验(RN 版本一致、rnu 不降级) +- [ ] 客户端 10.51.0 发版(admin 的 forceBoot 门槛与之对齐) +- [ ] 服务端已推送未激活:pushy 主机 `install-npm-release.sh` + + `service-cli.sh restart all`(验 serverVersion);cresc 跑 + `update-cloudrun.sh`;两个 admin 走各自 CI From f33bd6ac0582f1465e4bb587c34cb30a61aaf7f0 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 10 Aug 2026 00:39:44 +0800 Subject: [PATCH 20/29] docs: fold the second review round into the follow-ups list 181952a's re-review surfaced ten more confirmed items, six introduced by the fixes themselves; the open ones now live here: the config-sync revert race, the download-round deadline treated as one cross-platform design (Harmony missing it entirely, iOS still per-URL, Android checking after launch and starving the full-download budget), the dead alert for hash-less rollout entries, the silently swallowed noArtifact decline, and two small P4s (slash-variant duplicate requests, missing Harmony whole-call timeout). Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 73 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index f7af0c5d..700fe55d 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -1,7 +1,8 @@ # 原生冷启动检测:遗留改进项 > 来源:`agent/harden-native-check-update` 分支两轮评审 -> (f679e11 初次评审 10 项 → 181952a 修复 4 项、缓解 1 项)。 +> (f679e11 初评 10 项 → 181952a 修 4 缓解 1;181952a 复评又出 10 项, +> 其中 6 项为修复自身引入,已并入下文)。 > 本文只记**仍开放**的项;已修复项不再列。按优先级排序,每项含现状、 > 风险与建议修法。发版(10.51.0)前建议至少处理 P1;P2 可随小版本; > P3/P4 显式拍板"接受"也算关闭。 @@ -44,8 +45,33 @@ Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已 已撤回版本。 **建议修法**:响应到达时捕获 `responseAtSeconds`,作为参数传入 -`persistResponseCache` 写进 `ts`(三端各一行改动,JS 读侧无需变);顺手把 -600s 整轮 deadline 补齐到 iOS/Harmony,消除三端 parity 缺口。 +`persistResponseCache` 写进 `ts`(三端各一行改动,JS 读侧无需变)。 + +**复评补充(181952a 引入/暴露)——下载轮次时限应作为一个整体设计统一三端**: +1. 整轮 600s deadline 只落了 Android;iOS 仍逐 URL 600s(上界 + attempts×urls×600s,可达 90 分钟),Harmony 完全没有(慢滴 CDN 每 + <60s 一字节即可绕过下载任务的不活动看门狗,轮次可跑数小时); +2. Android 的 deadline 在下一个下载**已发起后**才检查——超时轮次仍会 + 多启动一个孤儿下载,浪费带宽且可能与 JS 重试并发写版本文件 + (修法:发起前先查); +3. 整轮预算会被 diff/pdiff 的失败耗尽,挤压救砖最后手段 full 的时间 + (原先 full 独享 600s)——建议 full 单独保底额度。 + +--- + +## P2 — JS 配置同步的回退竞态(181952a 引入) + +**现状**:`syncNativeConfig` 的去重把当前配置与**最后一次已完成**的写入 +(`syncedNativeConfigJson`)比较。 + +**风险**:已同步 A → setOptions 改为 B(写入在途)→ B 完成前又回退为 A: +回退因等于 `synced`(仍是 A)被直接丢弃,随后 B 完成、`synced=B`、pending +为空——原生 KV 永久持有错误的策略/endpoints(如 app 已切回 alert 却按 +silent 激活),直到未来某次不同值的 setOptions 才被纠正。 + +**建议修法**(改动极小):去重时把在途/待写值一并纳入比较,或写入完成回调里 +与"最新期望值"复核不符则重新入队。顺手删掉 `flushNativeConfig` 同步抛 +catch 里的死代码递归"重试"(pending 已清空,必然早退,误导维护者)。 --- @@ -94,6 +120,36 @@ Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已 --- +## P3 — 无 hash 的灰度条目从"静默忽略"变成"死弹窗"(181952a 语义变更的副作用) + +**现状**:`resolveCheckResult` 的非空 hash 守卫移除了旧的 +`undefined === undefined` 等价:服务端误配出无 hash 的灰度条目时,内置包 +设备(currentVersion 为空)从静默 upToDate 变为返回 `update:true` 且无 +hash——已被再生成的金标向量固化。 + +**风险**:alert 策略下每次检查都弹"发现新版本",点确认后 `decideDownload` +因 `!hash` 判 noUpdate——死弹窗,按钮无效,每次检查重现。 + +**建议修法**:若新语义有意(暴露服务端错配),在 provider 层把无 hash 的 +update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不视为可更新"。 +与下一条(noArtifact)同属"坏发布该以遥测暴露而非 UI 弹窗/静默"。 + +--- + +## P3 — `noArtifact` 分支静默返回,丢失 errorUpdate 遥测(181952a 引入) + +**现状**:decideDownload 新增的 noArtifact 拒绝让 `downloadUpdate` 无日志、 +无错误事件、无遥测地返回;旧的空 attempts 路径会上报 +`{type:'errorUpdate'}`。 + +**风险**:服务端发出 update:true 有 hash 但无任何产物 URL 的坏发布,从 +"控制台可见"退化为"客户端静默无事发生",坏发布隐形。 + +**建议修法**:noArtifact 分支恢复 report({type:'errorUpdate', ...}),保持 +用户无感但平台可见。 + +--- + ## P4 — bundleHash 未就绪窗口的请求指纹失配(记录即可) **现状**:JS 预取 bundleHash 未 settle 时构造的请求体缺 `bundleHash` 键, @@ -116,6 +172,17 @@ Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已 --- +## P4 — 若干小项(复评新增) + +- **斜杠变体 endpoint 重复请求**:尾斜杠归一化发生在 C++ 去重之后,且首轮 + 循环只 add 不查 `tried`——`https://u.example.com` 与 `…com/` 会对同一 + URL POST 两次,还白占 8 次 HTTP 上限中的两次。修法:归一化提前进纯层 + (`orderEndpointCandidates` 前),或首轮也查 `tried`。 +- **Harmony 检查请求缺整请求封顶**:只有 connect/read 超时,没有 Android + `callTimeout(15s)` / iOS 超时取消的对应物,慢滴响应可拖长每次尝试。 + +--- + ## 发版清单(非代码缺陷,勿遗漏) - [ ] e2e:坏 bundle → 原生拉修复版 → 下次启动复活的端到端用例 From 283dfd415314230962784e04c702857f029819e2 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 10 Aug 2026 00:56:22 +0800 Subject: [PATCH 21/29] fix: close native check review follow-ups --- NATIVE_CHECKUPDATE_DESIGN.md | 12 +- NATIVE_CHECK_FOLLOWUPS.md | 28 ++- .../modules/update/DownloadTask.java | 76 +++++-- .../modules/update/DownloadTaskParams.java | 3 + .../update/NativeCheckOrchestrator.java | 67 ++++-- .../modules/update/UpdateContext.java | 126 +++++++---- harmony/pushy/src/main/ets/DownloadTask.ts | 54 +++-- .../pushy/src/main/ets/DownloadTaskParams.ts | 3 + .../src/main/ets/NativeCheckOrchestrator.ts | 67 +++++- harmony/pushy/src/main/ets/UpdateContext.ts | 126 ++++++----- ios/RCTPushy/RCTPushy.mm | 207 ++++++++++++++---- ios/RCTPushy/RCTPushyDownloader.h | 1 + ios/RCTPushy/RCTPushyDownloader.mm | 7 +- src/__tests__/client.test.ts | 97 ++++++++ src/__tests__/provider.render.test.tsx | 18 ++ src/client.ts | 74 +++++-- src/provider.tsx | 15 +- 17 files changed, 747 insertions(+), 234 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 71ff0b92..4cf13396 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -335,18 +335,22 @@ appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` packageVersion / buildTime / uuid / supportedDiffVersion / bundleHash 缓存) → BuildCheckRequestBody(bundleHash 同步读缓存,缺省省略字段) → OrderEndpointCandidates(endpoints, 原生随机数) -→ 顺序请求,单个超时 10s、整轮 HTTP 最多 8 次;全失败 → 拉 queryUrls +→ 顺序请求,单个 connect/read 超时 10s、whole-call 15s、整轮 HTTP 最多 + 8 次;全失败 → 拉 queryUrls (任一成功即用)合并新候选,排除已失败的,再顺序一轮;仍失败 → 本轮放弃 → HandleCheckResponse(响应原文, identity, isDev=false)(已实现,含 info 透出) → action=download:按 attempts 顺序走现有下载器(diff→pdiff→full, - testUrls 语义由原生逐个尝试实现);成功 → setLocalHashInfo(info 的 + testUrls 语义由原生逐个尝试实现);diff/pdiff 共享 600s 绝对 deadline, + full 另有 600s 救砖预算;同 hash 完整版本在任务真正开跑时再次跳过,失败 + 清理不得删除已有 `.pushy-complete` 安装;成功 → setLocalHashInfo(info 的 name/description/metaInfo) → 按 afterDownload 决定是否 setNeedUpdate -→ 原生处理完成后,将响应原文 + 时间戳 + 请求/配置指纹落盘(§10.3) +→ 原生处理完成后,将响应原文 + **响应到达时刻** + 请求/配置指纹落盘 + (§10.3;下载耗时不得让旧响应获得新的时间戳) ``` ### 10.3 与 JS 的去重(§6 的落地形态) -首版**原生只写缓存**:响应原文 + 时间戳 + 请求/配置指纹落到固定文件。紧 +首版**原生只写缓存**:响应原文 + 响应到达时间戳 + 请求/配置指纹落到固定文件。紧 随其后的 JS 小改动:`checkUpdate` 先读该缓存,时间戳新鲜(暂定 2 分钟) 则直接复用不发请求。改造前的过渡期是双检查——多一次网络请求,服务端有 缓存,无害。 diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index 700fe55d..573b9f5f 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -3,9 +3,31 @@ > 来源:`agent/harden-native-check-update` 分支两轮评审 > (f679e11 初评 10 项 → 181952a 修 4 缓解 1;181952a 复评又出 10 项, > 其中 6 项为修复自身引入,已并入下文)。 -> 本文只记**仍开放**的项;已修复项不再列。按优先级排序,每项含现状、 -> 风险与建议修法。发版(10.51.0)前建议至少处理 P1;P2 可随小版本; -> P3/P4 显式拍板"接受"也算关闭。 +> 本文最初只记录开放项。2026-08-10 已完成代码项复核与修复;下文保留 +> 原始问题描述用于追溯,处理结论以紧随其后的状态表为准。发版动作仍保留在 +> 文末清单中,不因代码合入而自动视为完成。 + +## 2026-08-10 处理结论 + +| 项目 | 结论 | 落地方式 | +|---|---|---| +| P1 Android/Harmony 重复下载 | 已修复 | 任务真正开始时复查 `.pushy-complete` + bundle;失败清理检测到完整安装时不删目录。iOS 注册表同时升级为按 hash + artifact type 合流/排队 | +| P2 缓存 `ts` 锚点 | 已修复 | 三端在 check 响应到达时捕获时间,下载、补丁和激活结束后仍写该时间 | +| P2 下载轮次 deadline | 已修复 | 三端统一为 diff/pdiff 共享 600s、full 独享 600s;发起前检查绝对 deadline,下载任务在真正开跑时按剩余预算设置 whole-call timeout | +| P2 JS 配置回退竞态 | 已修复 | 在途写入期间仍记录与 `synced` 相同的最新期望值;B 完成后会继续把 A 写回;删除同步 throw 路径的无效递归 | +| P2 iOS 合流语义 | 已修复 | 同类型下载共享结果并向所有 join 者广播进度;不同类型按 hash 排队,避免把 diff 的失败错误归给 full | +| P3 `.pushy-complete` 迁移 | 接受一次性重下 | 不把仅有 bundle 的旧目录推断为完整安装,避免把半解压目录误标为成功;10.51.0 发布说明仍需明确这一流量成本 | +| P3 状态解析异常不调度 | 已修复 | 三端启动解析改为 finally 调度;正常路径保留最终 rollback 快照,异常路径使用空快照 | +| P3 无 hash 死弹窗 | 已修复 | Provider 将无 hash 的 update 降级为开发者日志/遥测与 `upToDate`,不再展示不可执行的确认按钮 | +| P3 `noArtifact` 静默 | 已修复 | 保持终端用户无感,同时恢复 `errorUpdate` 报告并携带目标 hash | +| P4 bundleHash 窗口 | 接受并记录 | `readNativeCheckCache` 已注明首启预取未完成时有意 cache miss,不为 hash 阻塞检查 | +| P4 缓存冗余解析 | 已修复 | 直接传入现成 `fetchBody`/native config 对象,只解析缓存中的字符串一侧 | +| P4 endpoint 斜杠重复 | 已修复 | 三端首轮请求也先查规范化后的 `tried` 集合,重复项不计入 8 次上限 | +| P4 Harmony 整请求超时 | 已修复 | check HTTP 增加 15s whole-call cap;更新下载增加绝对 deadline 并在超时后销毁请求 | + +代码验证基线:JS 完整回归 172 项、Biome/TypeScript/Harmony strict 类型检查、 +77 项 flow core ASan/UBSan、29 项 patch core、Android Release Java 编译、 +iOS Release simulator 构建均通过。 --- diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index ed6a5e88..c88a1bd9 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -107,14 +107,25 @@ private void downloadFile() throws IOException { this.hash = params.hash; String url = params.url; File writePath = params.targetFile; - UpdateFileUtils.ensureParentDirectory(writePath); Request request = new Request.Builder().url(url).build(); + OkHttpClient requestClient = HTTP_CLIENT; + if (params.deadlineNanos > 0) { + long remainingNanos = params.deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { + throw new IOException("Download deadline expired before start"); + } + requestClient = HTTP_CLIENT.newBuilder() + .callTimeout(remainingNanos, java.util.concurrent.TimeUnit.NANOSECONDS) + .build(); + } + + UpdateFileUtils.ensureParentDirectory(writePath); if (writePath.exists() && !writePath.delete()) { throw new IOException("Failed to replace existing file: " + writePath); } - try (Response response = HTTP_CLIENT.newCall(request).execute()) { + try (Response response = requestClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Server error: " + response.code() + " " + response.message()); } @@ -418,32 +429,53 @@ private boolean isPatchTask(int taskType) { || taskType == DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK; } + private boolean hasCompletedPatchDirectory() { + return params.unzipDirectory != null + && new File(params.unzipDirectory, "index.bundlejs").isFile() + && new File( + params.unzipDirectory, + UpdateContext.VERSION_COMPLETE_FILE + ).isFile(); + } + @Override public void run() { int taskType = params.type; + final boolean alreadyCompleted = isPatchTask(taskType) + && hasCompletedPatchDirectory(); try { - switch (taskType) { - case DownloadTaskParams.TASK_TYPE_PATCH_FULL: - doFullPatch(); - break; - case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK: - doPatchFromApk(); - break; - case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK: - doPatchFromPpk(); - break; - case DownloadTaskParams.TASK_TYPE_CLEANUP: - doCleanUp(); - break; - case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD: - downloadFile(); - break; - default: - break; + if (alreadyCompleted) { + Log.i(UpdateContext.TAG, + "download task: version " + params.hash + " already completed"); + } else { + switch (taskType) { + case DownloadTaskParams.TASK_TYPE_PATCH_FULL: + doFullPatch(); + break; + case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK: + doPatchFromApk(); + break; + case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK: + doPatchFromPpk(); + break; + case DownloadTaskParams.TASK_TYPE_CLEANUP: + doCleanUp(); + break; + case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD: + downloadFile(); + break; + default: + break; + } } } catch (Throwable error) { Log.e(UpdateContext.TAG, "download task failed", error); - cleanUpAfterFailure(taskType); + // A duplicate task must never delete a version completed by an + // earlier queued task. The marker + bundle pair is the ownership + // handoff: once present, this failure did not create that install. + if (!hasCompletedPatchDirectory()) { + cleanUpAfterFailure(taskType); + } if (params.listener != null) { // A patch task that failed after its artifact was fully @@ -462,7 +494,7 @@ && isPatchTask(taskType) return; } - if (isPatchTask(taskType)) { + if (isPatchTask(taskType) && !alreadyCompleted) { try { File marker = new File( params.unzipDirectory, diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java index 136761db..c741f7bc 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java @@ -20,6 +20,9 @@ class DownloadTaskParams { String originHash; // TASK_TYPE_CLEANUP only: entries younger than this survive; 0 = delete all int maxAgeDays = 3; + // Absolute System.nanoTime deadline for orchestrated cold-start downloads; + // 0 keeps the normal public API's 10-minute per-call timeout. + long deadlineNanos; File targetFile; File unzipDirectory; File originDirectory; diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java index f5f27778..bb1ae965 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -30,7 +30,7 @@ final class NativeCheckOrchestrator { // exact logical request and native config that produced it. static final String KEY_RESP_CACHE = "nativeCheckResp"; private static final int MAX_CHECK_HTTP_ATTEMPTS = 8; - private static final long DOWNLOAD_ROUND_TIMEOUT_SECONDS = 600; + private static final long DOWNLOAD_PHASE_TIMEOUT_SECONDS = 600; private static final AtomicBoolean scheduled = new AtomicBoolean(false); @@ -144,6 +144,9 @@ private static void runOnce( "native check: no endpoint reachable, giving up until next launch"); return; } + // Cache freshness is anchored to when the server response arrived, + // not to when a potentially long download/patch/activation finished. + final long responseAtSeconds = System.currentTimeMillis() / 1000; String decisionJson = NativeUpdateFlow.handleCheckResponse( responseText, identity.toString(), config.optString("afterDownload", "")); @@ -152,7 +155,8 @@ private static void runOnce( } JSONObject decision = new JSONObject(decisionJson); if (!"download".equals(decision.optString("action"))) { - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, configJson, body, responseText, responseAtSeconds); Log.i(UpdateContext.TAG, "native check: nothing to do (" + decision.optString("reason") + ")"); return; @@ -170,7 +174,8 @@ private static void runOnce( if (!downloaded) { // The native attempt has finished, so JS may safely reuse the // response and retry through its own strategy chain. - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, configJson, body, responseText, responseAtSeconds); return; } @@ -205,17 +210,19 @@ private static void runOnce( } // Publish the response only after native file/state work is complete; // otherwise JS can observe it and start a competing download. - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, configJson, body, responseText, responseAtSeconds); } private static void persistResponseCache( UpdateContext context, String configJson, String requestBody, - String responseText + String responseText, + long responseAtSeconds ) throws JSONException { JSONObject cacheEntry = new JSONObject(); - cacheEntry.put("ts", System.currentTimeMillis() / 1000); + cacheEntry.put("ts", responseAtSeconds); cacheEntry.put("body", responseText); cacheEntry.put("request", requestBody); cacheEntry.put("config", configJson); @@ -287,13 +294,12 @@ private static String runCheckRequest(JSONObject config, String appKey, String b int httpAttempts = 0; for (int i = 0; i < ordered.length(); i++) { String base = normalizeEndpointBase(ordered.optString(i, "")); - if (base.isEmpty()) { + if (base.isEmpty() || !tried.add(base)) { continue; } if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { return null; } - tried.add(base); String response = httpRequest(base + "/checkUpdate/" + appKey, body); if (isValidCheckResponse(response)) { return response; @@ -347,8 +353,9 @@ private static boolean performAttempts( if (attempts == null) { return false; } - final long deadlineNanos = System.nanoTime() - + TimeUnit.SECONDS.toNanos(DOWNLOAD_ROUND_TIMEOUT_SECONDS); + final long incrementalDeadlineNanos = System.nanoTime() + + TimeUnit.SECONDS.toNanos(DOWNLOAD_PHASE_TIMEOUT_SECONDS); + long fullDeadlineNanos = 0; for (int i = 0; i < attempts.length(); i++) { JSONObject attempt = attempts.optJSONObject(i); if (attempt == null) { @@ -359,6 +366,15 @@ private static boolean performAttempts( // diff patches from the running version; none is running. continue; } + final boolean isFullAttempt = "full".equals(type); + if (isFullAttempt && fullDeadlineNanos == 0) { + // Incremental failures must not consume the last-resort full + // download's budget. Each phase gets one bounded 10min window. + fullDeadlineNanos = System.nanoTime() + + TimeUnit.SECONDS.toNanos(DOWNLOAD_PHASE_TIMEOUT_SECONDS); + } + final long deadlineNanos = isFullAttempt + ? fullDeadlineNanos : incrementalDeadlineNanos; JSONArray urls = attempt.optJSONArray("urls"); if (urls == null) { continue; @@ -368,6 +384,15 @@ private static boolean performAttempts( if (url.isEmpty()) { continue; } + // Check before enqueueing: once the phase budget is gone we + // must not launch an orphan download that outlives the round. + long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { + if (isFullAttempt) { + return false; + } + break; + } final CountDownLatch latch = new CountDownLatch(1); final AtomicBoolean succeeded = new AtomicBoolean(false); final String attemptType = type; @@ -387,21 +412,23 @@ public void onDownloadFailed(Throwable error) { } }; if ("diff".equals(type)) { - context.downloadPatchFromPpk(url, hash, originHash, listener); + context.downloadPatchFromPpk( + url, hash, originHash, listener, deadlineNanos); } else if ("pdiff".equals(type)) { - context.downloadPatchFromApk(url, hash, listener); + context.downloadPatchFromApk( + url, hash, listener, deadlineNanos); } else { - context.downloadFullUpdate(url, hash, listener); + context.downloadFullUpdate( + url, hash, listener, deadlineNanos); } try { - // One deadline covers the complete diff→pdiff→full round; - // each DownloadTask also has its own per-call timeout. - long remainingNanos = deadlineNanos - System.nanoTime(); - if (remainingNanos <= 0 - || !latch.await(remainingNanos, TimeUnit.NANOSECONDS)) { + if (!latch.await(remainingNanos, TimeUnit.NANOSECONDS)) { Log.w(UpdateContext.TAG, - "native check: download round timed out during " + type); - return false; + "native check: download phase timed out during " + type); + if (isFullAttempt) { + return false; + } + break; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index d63a4c0d..83ea8a09 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -241,6 +241,12 @@ public interface DownloadFileListener { } public void downloadFullUpdate(String url, String hash, DownloadFileListener listener) { + downloadFullUpdate(url, hash, listener, 0); + } + + void downloadFullUpdate( + String url, String hash, DownloadFileListener listener, long deadlineNanos + ) { if (rejectUnsafeComponent(hash, listener)) { return; } @@ -249,6 +255,7 @@ public void downloadFullUpdate(String url, String hash, DownloadFileListener lis params.url = url; params.hash = hash; params.listener = listener; + params.deadlineNanos = deadlineNanos; params.targetFile = new File(rootDir, hash + ".ppk"); params.unzipDirectory = new File(rootDir, hash); enqueue(params); @@ -276,6 +283,12 @@ public void downloadFile(String url, String hash, String fileName, DownloadFileL } public void downloadPatchFromApk(String url, String hash, DownloadFileListener listener) { + downloadPatchFromApk(url, hash, listener, 0); + } + + void downloadPatchFromApk( + String url, String hash, DownloadFileListener listener, long deadlineNanos + ) { if (rejectUnsafeComponent(hash, listener)) { return; } @@ -284,12 +297,23 @@ public void downloadPatchFromApk(String url, String hash, DownloadFileListener l params.url = url; params.hash = hash; params.listener = listener; + params.deadlineNanos = deadlineNanos; params.targetFile = new File(rootDir, hash + ".apk.patch"); params.unzipDirectory = new File(rootDir, hash); enqueue(params); } public void downloadPatchFromPpk(String url, String hash, String originHash, DownloadFileListener listener) { + downloadPatchFromPpk(url, hash, originHash, listener, 0); + } + + void downloadPatchFromPpk( + String url, + String hash, + String originHash, + DownloadFileListener listener, + long deadlineNanos + ) { if (rejectUnsafeComponent(hash, listener) || rejectUnsafeComponent(originHash, listener)) { return; } @@ -299,6 +323,7 @@ public void downloadPatchFromPpk(String url, String hash, String originHash, Dow params.hash = hash; params.originHash = originHash; params.listener = listener; + params.deadlineNanos = deadlineNanos; params.targetFile = new File(rootDir, originHash + "-" + hash + ".ppk.patch"); params.unzipDirectory = new File(rootDir, hash); params.originDirectory = new File(rootDir, originHash); @@ -533,58 +558,67 @@ public String getBundleUrl() { public String getBundleUrl(String defaultAssetsUrl) { isUsingBundleUrl = true; - StateCoreResult currentState = getStateSnapshot(); - StateCoreResult launchState = runStateCore( - STATE_OP_RESOLVE_LAUNCH, - currentState, - null, - ignoreRollback, - true - ); - if (launchState.didRollback) { - // The crash-protection rollback: the new version never called - // markSuccess. Keep this visible in release logs. - Log.e(TAG, "Version " + currentState.currentVersion - + " was not marked as successful, rolling back to " - + launchState.currentVersion); - } - if (launchState.didRollback || launchState.consumedFirstTime) { - SharedPreferences.Editor editor = sp.edit(); - applyState(editor, launchState); + String nativeCheckRolledBackVersion = null; + try { + StateCoreResult currentState = getStateSnapshot(); + StateCoreResult launchState = runStateCore( + STATE_OP_RESOLVE_LAUNCH, + currentState, + null, + ignoreRollback, + true + ); + nativeCheckRolledBackVersion = launchState.rolledBackVersion; + if (launchState.didRollback) { + // The crash-protection rollback: the new version never called + // markSuccess. Keep this visible in release logs. + Log.e(TAG, "Version " + currentState.currentVersion + + " was not marked as successful, rolling back to " + + launchState.currentVersion); + } + if (launchState.didRollback || launchState.consumedFirstTime) { + SharedPreferences.Editor editor = sp.edit(); + applyState(editor, launchState); + if (launchState.consumedFirstTime) { + editor.putBoolean(KEY_FIRST_LOAD_MARKED, true); + } + persistEditor(editor, "resolve launch"); + } if (launchState.consumedFirstTime) { - editor.putBoolean(KEY_FIRST_LOAD_MARKED, true); + // bundleURL may be resolved multiple times in one process. + ignoreRollback = true; } - persistEditor(editor, "resolve launch"); - } - if (launchState.consumedFirstTime) { - // bundleURL may be resolved multiple times in one process. - ignoreRollback = true; - } - String currentVersion = launchState.loadVersion; - if (currentVersion == null) { - NativeCheckOrchestrator.schedule(this, launchState.rolledBackVersion); - return defaultAssetsUrl; - } + String currentVersion = launchState.loadVersion; + if (currentVersion == null) { + return defaultAssetsUrl; + } - // Guard the rollback chain against cycles: a corrupted state returning - // an already-visited version would otherwise spin this loop forever on - // the main thread. - java.util.HashSet visitedVersions = new java.util.HashSet<>(); - while (currentVersion != null && visitedVersions.add(currentVersion)) { - File bundleFile = new File(rootDir, currentVersion+"/index.bundlejs"); - if (!bundleFile.exists()) { - Log.e(TAG, "Bundle version " + currentVersion + " not found."); - currentVersion = this.rollBack(); - continue; + // Guard the rollback chain against cycles: a corrupted state returning + // an already-visited version would otherwise spin this loop forever on + // the main thread. + java.util.HashSet visitedVersions = new java.util.HashSet<>(); + while (currentVersion != null && visitedVersions.add(currentVersion)) { + File bundleFile = new File(rootDir, currentVersion+"/index.bundlejs"); + if (!bundleFile.exists()) { + Log.e(TAG, "Bundle version " + currentVersion + " not found."); + currentVersion = this.rollBack(); + nativeCheckRolledBackVersion = rolledBackVersion(); + continue; + } + launchVersion = currentVersion; + nativeCheckRolledBackVersion = rolledBackVersion(); + return bundleFile.toString(); } - launchVersion = currentVersion; - NativeCheckOrchestrator.schedule(this, rolledBackVersion()); - return bundleFile.toString(); - } - NativeCheckOrchestrator.schedule(this, rolledBackVersion()); - return defaultAssetsUrl; + nativeCheckRolledBackVersion = rolledBackVersion(); + return defaultAssetsUrl; + } finally { + // Even corrupted state or a state-core exception must not disable + // the next-launch rescue check. A null snapshot simply omits the + // rollback guard for this exceptional launch. + NativeCheckOrchestrator.schedule(this, nativeCheckRolledBackVersion); + } } boolean hasCompletedVersion(String hash) { diff --git a/harmony/pushy/src/main/ets/DownloadTask.ts b/harmony/pushy/src/main/ets/DownloadTask.ts index 06ddc627..4bdf462e 100644 --- a/harmony/pushy/src/main/ets/DownloadTask.ts +++ b/harmony/pushy/src/main/ets/DownloadTask.ts @@ -97,6 +97,7 @@ const DIFF_MANIFEST_ENTRY = '__diff.json'; const HARMONY_BUNDLE_PATCH_ENTRY = 'bundle.harmony.js.patch'; const TEMP_ORIGIN_BUNDLE_ENTRY = '.origin.bundle.harmony.js'; const FILE_COPY_BUFFER_SIZE = 64 * 1024; +const DOWNLOAD_CALL_TIMEOUT_MS = 10 * 60 * 1000; export class DownloadTask { private context: common.Context; @@ -345,6 +346,12 @@ export class DownloadTask { let writeQueue = Promise.resolve(); let lastReportedPercentage = -1; let lastReportedBytes = 0; + const deadlineAtMs = params.deadlineAtMs > 0 + ? params.deadlineAtMs + : Date.now() + DOWNLOAD_CALL_TIMEOUT_MS; + if (deadlineAtMs <= Date.now()) { + throw Error('Download deadline expired before start'); + } // Emit at most one progress event per whole-percent change (or per 256KB // when the total is unknown), and only from the dataReceive handler, so the @@ -376,6 +383,7 @@ export class DownloadTask { // Promise (and the JS caller) forever. const INACTIVITY_TIMEOUT_MS = 60000; let watchdogTimer: number | null = null; + let deadlineTimer: number | null = null; let inactivityReject: ((error: Error) => void) | null = null; const clearWatchdog = () => { if (watchdogTimer !== null) { @@ -484,14 +492,22 @@ export class DownloadTask { }, ); - const responseCode = await httpRequest.requestInStream(params.url, { - method: http.RequestMethod.GET, - readTimeout: 60000, - connectTimeout: 60000, - header: { - 'Content-Type': 'application/octet-stream', - }, + const deadlinePromise = new Promise((_, reject) => { + deadlineTimer = setTimeout(() => { + reject(Error('Download exceeded its whole-call deadline')); + }, Math.max(1, deadlineAtMs - Date.now())); }); + const responseCode = await Promise.race([ + httpRequest.requestInStream(params.url, { + method: http.RequestMethod.GET, + readTimeout: 60000, + connectTimeout: 60000, + header: { + 'Content-Type': 'application/octet-stream', + }, + }), + deadlinePromise, + ]); if (responseCode > 299) { throw Error(`Server error: ${responseCode}`); } @@ -501,7 +517,7 @@ export class DownloadTask { // 时 reject(unhandled rejection),且 promise 一经 reject 无法复活—— // 即使随后数据正常流入,race 也必然以 "Download stalled" 失败。 refreshWatchdog(); - await Promise.race([dataEndPromise, inactivityPromise]); + await Promise.race([dataEndPromise, inactivityPromise, deadlinePromise]); const stats = await fileIo.stat(params.targetFile); const fileSize = stats.size; if (contentLength > 0 && fileSize !== contentLength) { @@ -514,6 +530,9 @@ export class DownloadTask { throw error; } finally { clearWatchdog(); + if (deadlineTimer !== null) { + clearTimeout(deadlineTimer); + } try { await closeWriter(); } catch (closeError) { @@ -800,6 +819,10 @@ export class DownloadTask { } public async execute(params: DownloadTaskParams): Promise { + const isPatchTask = + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK; try { switch (params.type) { case DownloadTaskParams.TASK_TYPE_PATCH_FULL: @@ -820,11 +843,7 @@ export class DownloadTask { default: throw Error(`Unknown task type: ${params.type}`); } - if ( - params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL || - params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP || - params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK - ) { + if (isPatchTask) { await this.writeFileContent( `${params.unzipDirectory}/${VERSION_COMPLETE_FILE_NAME}`, new Uint8Array(0), @@ -836,7 +855,14 @@ export class DownloadTask { try { if (params.type === DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD) { await fileIo.unlink(params.targetFile); - } else { + } else if ( + !fileIo.accessSync( + `${params.unzipDirectory}/${VERSION_COMPLETE_FILE_NAME}`, + ) || + !fileIo.accessSync(`${params.unzipDirectory}/bundle.harmony.js`) + ) { + // Never let a failed duplicate task remove an install already + // handed off by an earlier task via marker + bundle. await this.removeDirectory(params.unzipDirectory); } } catch (cleanupError: any) { diff --git a/harmony/pushy/src/main/ets/DownloadTaskParams.ts b/harmony/pushy/src/main/ets/DownloadTaskParams.ts index 1f302564..6494d6dd 100644 --- a/harmony/pushy/src/main/ets/DownloadTaskParams.ts +++ b/harmony/pushy/src/main/ets/DownloadTaskParams.ts @@ -16,4 +16,7 @@ export class DownloadTaskParams { targetFile: string = ''; // 目标文件路径 unzipDirectory: string = ''; // 解压目录路径 originDirectory: string = ''; // 原始文件目录路径 + // Native cold-start orchestrator's absolute wall-clock deadline. Zero uses + // the normal public download API's 10-minute whole-call cap. + deadlineAtMs: number = 0; } diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index 1545949e..a94dcf2a 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -17,8 +17,10 @@ export const KEY_CONFIG = 'nativeConfig'; // 供 JS 侧复用的原始响应缓存(§10.3),同时记录请求与配置指纹以限定命中范围。 export const KEY_RESP_CACHE = 'nativeCheckResp'; const REQUEST_TIMEOUT_MS = 10000; +const REQUEST_CALL_TIMEOUT_MS = 15000; const MAX_CHECK_HTTP_ATTEMPTS = 8; const START_DELAY_MS = 5000; +const DOWNLOAD_PHASE_TIMEOUT_MS = 10 * 60 * 1000; const DOWNLOAD_TYPE_DIFF = 'diff'; const DOWNLOAD_TYPE_PDIFF = 'pdiff'; @@ -168,6 +170,8 @@ async function runOnce( logger.debug(TAG, 'no endpoint reachable, giving up until next launch'); return; } + // Anchor cache freshness to response arrival, before download/patch work. + const responseAtSeconds = Math.floor(Date.now() / 1000); const decisionJson = NativePatchCore.handleCheckResponse( responseText, @@ -179,7 +183,13 @@ async function runOnce( } const decision = JSON.parse(decisionJson) as Decision; if (decision.action !== 'download') { - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, + configJson, + body, + responseText, + responseAtSeconds, + ); logger.debug(TAG, `nothing to do (${decision.reason ?? ''})`); return; } @@ -198,7 +208,13 @@ async function runOnce( ); } if (!downloaded) { - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, + configJson, + body, + responseText, + responseAtSeconds, + ); return; } @@ -231,7 +247,13 @@ async function runOnce( logger.debug(TAG, `downloaded ${hash}, activation left to JS`); } // 仅在原生文件/状态工作结束后公开缓存,避免 JS 观察到响应后并发下载。 - persistResponseCache(context, configJson, body, responseText); + persistResponseCache( + context, + configJson, + body, + responseText, + responseAtSeconds, + ); } function persistResponseCache( @@ -239,9 +261,10 @@ function persistResponseCache( configJson: string, requestBody: string, responseText: string, + responseAtSeconds: number, ): void { const cacheEntry: RespCacheEntry = { - ts: Math.floor(Date.now() / 1000), + ts: responseAtSeconds, body: responseText, request: requestBody, config: configJson, @@ -266,12 +289,13 @@ async function httpRequest( postBody?: string, ): Promise { const client = http.createHttp(); + let callTimer: number | null = null; try { const header: Record = { Accept: 'application/json' }; if (postBody !== undefined) { header['Content-Type'] = 'application/json'; } - const response = await client.request(url, { + const requestPromise = client.request(url, { method: postBody !== undefined ? http.RequestMethod.POST : http.RequestMethod.GET, @@ -281,6 +305,12 @@ async function httpRequest( readTimeout: REQUEST_TIMEOUT_MS, expectDataType: http.HttpDataType.STRING, }); + const timeoutPromise = new Promise((_, reject) => { + callTimer = setTimeout(() => { + reject(Error(`HTTP request exceeded ${REQUEST_CALL_TIMEOUT_MS}ms`)); + }, REQUEST_CALL_TIMEOUT_MS); + }); + const response = await Promise.race([requestPromise, timeoutPromise]); if ( response.responseCode >= 200 && response.responseCode < 300 && @@ -292,6 +322,9 @@ async function httpRequest( } catch (e) { return undefined; } finally { + if (callTimer !== null) { + clearTimeout(callTimer); + } client.destroy(); } } @@ -316,7 +349,7 @@ async function runCheckRequest( let httpAttempts = 0; for (const rawBase of ordered) { const base = normalizeEndpointBase(rawBase); - if (!base) { + if (!base || tried.has(base)) { continue; } if (httpAttempts++ >= MAX_CHECK_HTTP_ATTEMPTS) { @@ -382,23 +415,39 @@ async function performAttempts( hash: string, originHash: string, ): Promise { + const incrementalDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS; + let fullDeadline = 0; for (const attempt of attempts) { const type = attempt.type ?? ''; if (type === DOWNLOAD_TYPE_DIFF && !originHash) { // diff 以当前运行版本为源;没有运行中的热更版本就跳过。 continue; } + const isFullAttempt = + type !== DOWNLOAD_TYPE_DIFF && type !== DOWNLOAD_TYPE_PDIFF; + if (isFullAttempt && fullDeadline === 0) { + // Preserve a full 10min rescue budget even when diff/pdiff exhausted + // their own phase window. + fullDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS; + } + const deadline = isFullAttempt ? fullDeadline : incrementalDeadline; for (const url of attempt.urls ?? []) { if (!url) { continue; } + if (Date.now() >= deadline) { + if (isFullAttempt) { + return false; + } + break; + } try { if (type === DOWNLOAD_TYPE_DIFF) { - await context.downloadPatchFromPpk(url, hash, originHash); + await context.downloadPatchFromPpk(url, hash, originHash, deadline); } else if (type === DOWNLOAD_TYPE_PDIFF) { - await context.downloadPatchFromPackage(url, hash); + await context.downloadPatchFromPackage(url, hash, deadline); } else { - await context.downloadFullUpdate(url, hash); + await context.downloadFullUpdate(url, hash, deadline); } return true; } catch (e) { diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index c3fec8e0..6489f15b 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -324,6 +324,15 @@ export class UpdateContext { private async executeTask(params: DownloadTaskParams): Promise { await this.enqueueSerialTask(() => { + const isPatchTask = + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK; + // Re-check only when this queued job actually starts. A JS download may + // have completed while the cold-start duplicate waited behind it. + if (isPatchTask && this.hasDownloadedVersion(params.hash)) { + return Promise.resolve(); + } const downloadTask = new DownloadTask(this.context); return downloadTask.execute(params); }); @@ -455,7 +464,11 @@ export class UpdateContext { this.trace('resetToPackagedBundle:after'); } - public async downloadFullUpdate(url: string, hash: string): Promise { + public async downloadFullUpdate( + url: string, + hash: string, + deadlineAtMs: number = 0, + ): Promise { try { const params = this.createTaskParams( DownloadTaskParams.TASK_TYPE_PATCH_FULL, @@ -464,6 +477,7 @@ export class UpdateContext { ); params.targetFile = `${this.rootDir}/${hash}.ppk`; params.unzipDirectory = `${this.rootDir}/${hash}`; + params.deadlineAtMs = deadlineAtMs; await this.executeTask(params); } catch (e) { console.error('Failed to download full update:', e); @@ -489,6 +503,7 @@ export class UpdateContext { url: string, hash: string, originHash: string, + deadlineAtMs: number = 0, ): Promise { const params = this.createTaskParams( DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK, @@ -499,12 +514,14 @@ export class UpdateContext { params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`; params.unzipDirectory = `${this.rootDir}/${hash}`; params.originDirectory = `${this.rootDir}/${params.originHash}`; + params.deadlineAtMs = deadlineAtMs; await this.executeTask(params); } public async downloadPatchFromPackage( url: string, hash: string, + deadlineAtMs: number = 0, ): Promise { try { const params = this.createTaskParams( @@ -514,6 +531,7 @@ export class UpdateContext { ); params.targetFile = `${this.rootDir}/${hash}.app.patch`; params.unzipDirectory = `${this.rootDir}/${hash}`; + params.deadlineAtMs = deadlineAtMs; return await this.executeTask(params); } catch (e) { console.error('Failed to download package patch:', e); @@ -565,60 +583,70 @@ export class UpdateContext { public getBundleUrl() { UpdateContext.isUsingBundleUrl = true; this.trace('getBundleUrl:enter'); - const stateBeforeLaunch = this.getStateSnapshot(); - const launchState = NativePatchCore.runStateCore( - STATE_OP_RESOLVE_LAUNCH, - stateBeforeLaunch, - '', - UpdateContext.ignoreRollback, - true, - ); - if (launchState.didRollback) { - // The crash-protection rollback: the new version never called - // markSuccess. Keep this visible in release logs. - console.error( - `Version ${stateBeforeLaunch.currentVersion} was not marked as successful,` + - ` rolled back to ${launchState.currentVersion}`, + let nativeCheckRolledBackVersion = ''; + try { + const stateBeforeLaunch = this.getStateSnapshot(); + const launchState = NativePatchCore.runStateCore( + STATE_OP_RESOLVE_LAUNCH, + stateBeforeLaunch, + '', + UpdateContext.ignoreRollback, + true, + ); + nativeCheckRolledBackVersion = launchState.rolledBackVersion || ''; + if (launchState.didRollback) { + // The crash-protection rollback: the new version never called + // markSuccess. Keep this visible in release logs. + console.error( + `Version ${stateBeforeLaunch.currentVersion} was not marked as successful,` + + ` rolled back to ${launchState.currentVersion}`, + ); + } + if (launchState.didRollback || launchState.consumedFirstTime) { + this.persistState(launchState, { + markFirstLoadMarker: launchState.consumedFirstTime, + }); + } + if (launchState.consumedFirstTime) { + UpdateContext.ignoreRollback = true; + } + this.trace( + `getBundleUrl:load=${launchState.loadVersion}` + + ` consumed=${launchState.consumedFirstTime}` + + ` rollback=${launchState.didRollback}`, ); - } - if (launchState.didRollback || launchState.consumedFirstTime) { - this.persistState(launchState, { - markFirstLoadMarker: launchState.consumedFirstTime, - }); - } - if (launchState.consumedFirstTime) { - UpdateContext.ignoreRollback = true; - } - this.trace( - `getBundleUrl:load=${launchState.loadVersion}` + - ` consumed=${launchState.consumedFirstTime}` + - ` rollback=${launchState.didRollback}`, - ); - let version = launchState.loadVersion || ''; - // Guard the rollback chain against cycles: a corrupted state returning an - // already-visited version would otherwise spin this loop forever during - // startup (Android has the same guard). - const visitedVersions = new Set(); - while (version && !visitedVersions.has(version)) { - visitedVersions.add(version); - const bundleFile = this.getBundlePath(version); - try { - if (!fileIo.accessSync(bundleFile)) { - console.error(`Bundle version ${version} not found.`); + let version = launchState.loadVersion || ''; + // Guard the rollback chain against cycles: a corrupted state returning an + // already-visited version would otherwise spin this loop forever during + // startup (Android has the same guard). + const visitedVersions = new Set(); + while (version && !visitedVersions.has(version)) { + visitedVersions.add(version); + const bundleFile = this.getBundlePath(version); + try { + if (!fileIo.accessSync(bundleFile)) { + console.error(`Bundle version ${version} not found.`); + version = this.rollBack(); + nativeCheckRolledBackVersion = this.rolledBackVersion(); + continue; + } + UpdateContext.launchVersion = version; + nativeCheckRolledBackVersion = this.rolledBackVersion(); + return bundleFile; + } catch (e) { + console.error('Failed to access bundle file:', e); version = this.rollBack(); - continue; + nativeCheckRolledBackVersion = this.rolledBackVersion(); } - UpdateContext.launchVersion = version; - scheduleNativeCheck(this, this.rolledBackVersion()); - return bundleFile; - } catch (e) { - console.error('Failed to access bundle file:', e); - version = this.rollBack(); } + nativeCheckRolledBackVersion = this.rolledBackVersion(); + return ''; + } finally { + // State corruption is exactly when the native rescue check is needed; + // schedule even if state parsing/rollback throws before a normal exit. + scheduleNativeCheck(this, nativeCheckRolledBackVersion); } - scheduleNativeCheck(this, this.rolledBackVersion()); - return ''; } public getCurrentVersion(): string { diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index c7d46664..87475900 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -91,8 +91,8 @@ typedef NS_ENUM(NSInteger, PushyType) { // but they must still share one download per target hash. Without this // process-wide registry two NSURLSessionDownloadTasks race over the same // archive path and the later unzip can delete the first task's valid output. -static NSMutableDictionary *PushyInFlightDownloads(void) { - static NSMutableDictionary *downloads; +static NSMutableDictionary *PushyInFlightDownloads(void) { + static NSMutableDictionary *downloads; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ downloads = [NSMutableDictionary dictionary]; @@ -100,26 +100,80 @@ typedef NS_ENUM(NSInteger, PushyType) { return downloads; } -static BOOL PushyRegisterDownload(NSString *hash, void (^callback)(NSError *)) { +typedef NS_ENUM(NSInteger, PushyDownloadRegistration) { + PushyDownloadRegistrationOwner, + PushyDownloadRegistrationJoined, + PushyDownloadRegistrationDeferred, +}; + +static PushyDownloadRegistration PushyRegisterDownload( + NSString *hash, + NSInteger type, + void (^callback)(NSError *), + void (^progress)(long long, long long), + void (^deferredStart)(void) +) { NSMutableDictionary *downloads = PushyInFlightDownloads(); @synchronized (downloads) { - NSMutableArray *callbacks = downloads[hash]; - if (callbacks != nil) { - [callbacks addObject:[callback copy]]; - return NO; + NSMutableDictionary *entry = downloads[hash]; + if (entry != nil) { + if ([entry[@"type"] integerValue] == type) { + [entry[@"callbacks"] addObject:[callback copy]]; + if (progress != nil) { + [entry[@"progress"] addObject:[progress copy]]; + } + return PushyDownloadRegistrationJoined; + } + // A diff failure must not settle a joined full request. Queue the + // different artifact type behind the owner; once restarted it + // registers normally (and re-checks the completion marker). + [entry[@"deferred"] addObject:[deferredStart copy]]; + return PushyDownloadRegistrationDeferred; } - downloads[hash] = [NSMutableArray arrayWithObject:[callback copy]]; - return YES; + NSMutableArray *progressHandlers = [NSMutableArray array]; + if (progress != nil) { + [progressHandlers addObject:[progress copy]]; + } + downloads[hash] = [@{ + @"type": @(type), + @"callbacks": [NSMutableArray arrayWithObject:[callback copy]], + @"progress": progressHandlers, + @"deferred": [NSMutableArray array], + } mutableCopy]; + return PushyDownloadRegistrationOwner; + } +} + +static void PushyReportDownloadProgress( + NSString *hash, long long received, long long total +) { + NSMutableDictionary *downloads = PushyInFlightDownloads(); + NSArray *handlers = nil; + @synchronized (downloads) { + handlers = [downloads[hash][@"progress"] copy]; + } + for (id value in handlers) { + void (^handler)(long long, long long) = + (void (^)(long long, long long))value; + handler(received, total); } } static void PushyFinishDownload(NSString *hash, NSError *error) { NSMutableDictionary *downloads = PushyInFlightDownloads(); NSArray *callbacks = nil; + NSArray *deferred = nil; @synchronized (downloads) { - callbacks = [downloads[hash] copy]; + callbacks = [downloads[hash][@"callbacks"] copy]; + deferred = [downloads[hash][@"deferred"] copy]; [downloads removeObjectForKey:hash]; } + // Establish the next (different-type) owner before waking the completed + // owner's callbacks; otherwise its strategy loop could race the waiter. + for (id value in deferred) { + void (^start)(void) = (void (^)(void))value; + start(); + } for (id value in callbacks) { void (^callback)(NSError *) = (void (^)(NSError *))value; callback(error); @@ -135,8 +189,11 @@ static void PushyFinishDownload(NSString *hash, NSError *error) { static void PushyWithStateLock(void (NS_NOESCAPE ^block)(void)) { os_unfair_lock_lock(&pushyStateLock); - block(); - os_unfair_lock_unlock(&pushyStateLock); + @try { + block(); + } @finally { + os_unfair_lock_unlock(&pushyStateLock); + } } static std::string PushyToStdString(NSString *value) { @@ -349,7 +406,8 @@ + (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion; + (void)runOnce:(NSString *)launchRolledBackVersion; + (void)persistResponseCache:(NSString *)responseText request:(NSString *)requestBody - config:(NSString *)configJson; + config:(NSString *)configJson + responseAt:(long long)responseAtSeconds; @end // Shared by the getBundleHash RCT method and the native cold-start check. @@ -397,8 +455,9 @@ + (NSURL *)bundleURL { __block NSURL *resolvedURL = nil; __block NSString *launchRolledBackVersion = nil; - PushyWithStateLock(^{ - NSUserDefaults *defaults = PushyDefaults(); + @try { + PushyWithStateLock(^{ + NSUserDefaults *defaults = PushyDefaults(); NSString *curPackageVersion = [RCTPushy packageVersion]; NSString *curBuildTime = [RCTPushy buildTime]; @@ -464,11 +523,15 @@ + (NSURL *)bundleURL // Capture before constantsToExport consumes this one-shot marker. // The delayed native check must never forceBoot the version that this // launch just rolled back. - launchRolledBackVersion = PushyFromStdString(state.rolled_back_version); - }); - - [RCTPushyOrchestrator scheduleFromColdStart:launchRolledBackVersion]; - return resolvedURL ?: [RCTPushy binaryBundleURL]; + launchRolledBackVersion = PushyFromStdString(state.rolled_back_version); + }); + return resolvedURL ?: [RCTPushy binaryBundleURL]; + } @finally { + // State corruption is exactly when the rescue path matters most. If + // resolution throws before a snapshot exists, nil safely omits only + // this launch's rollback guard instead of disabling the check. + [RCTPushyOrchestrator scheduleFromColdStart:launchRolledBackVersion]; + } } + (NSString *) rollback { @@ -850,8 +913,34 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( return; } - if (!PushyRegisterDownload(hash, callback)) { - RCTLogInfo(@"RCTPushy -- join in-flight download for %@", hash); + NSString *unzipDir = [dir stringByAppendingPathComponent:hash]; + NSString *completedBundle = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]; + NSString *completedMarker = [unzipDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; + if ([[NSFileManager defaultManager] fileExistsAtPath:completedBundle] + && [[NSFileManager defaultManager] fileExistsAtPath:completedMarker]) { + callback(nil); + return; + } + + void (^progress)(long long, long long) = ^(long long receivedBytes, long long totalBytes) { + if (self->hasListeners) { + [self sendEventWithName:EVENT_PROGRESS_DOWNLOAD body:@{ + PARAM_PROGRESS_HASH:hash, + PARAM_PROGRESS_RECEIVED:@(receivedBytes), + PARAM_PROGRESS_TOTAL:@(totalBytes), + }]; + } + }; + void (^deferredStart)(void) = ^{ + [self performUpdate:type options:options callback:callback]; + }; + PushyDownloadRegistration registration = PushyRegisterDownload( + hash, type, callback, progress, deferredStart); + if (registration != PushyDownloadRegistrationOwner) { + RCTLogInfo( + @"RCTPushy -- %@ in-flight download for %@", + registration == PushyDownloadRegistrationJoined ? @"join" : @"defer", + hash); return; } @@ -861,7 +950,6 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( // do: a half-unzipped/half-patched dir leaks disk and could later be // mistaken for a complete version. hash is validated non-blank above, so // this can never resolve to the download root itself. - NSString *unzipDir = [dir stringByAppendingPathComponent:hash]; void (^completion)(NSError *) = ^(NSError *error) { // Settle every JS/native waiter only after cleanup or the atomic // completion marker write has run on the process-wide file queue. @@ -887,14 +975,26 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( }; RCTLogInfo(@"RCTPushy -- download file %@", updateUrl); - [RCTPushyDownloader download:updateUrl savePath:zipFilePath progressHandler:^(long long receivedBytes, long long totalBytes) { - if (self->hasListeners) { - [self sendEventWithName:EVENT_PROGRESS_DOWNLOAD body:@{ - PARAM_PROGRESS_HASH:hash, - PARAM_PROGRESS_RECEIVED:[NSNumber numberWithLongLong:receivedBytes], - PARAM_PROGRESS_TOTAL:[NSNumber numberWithLongLong:totalBytes] - }]; + NSTimeInterval timeoutSeconds = 600; + NSNumber *deadlineAt = options[@"deadlineAt"]; + if ([deadlineAt isKindOfClass:[NSNumber class]]) { + timeoutSeconds = deadlineAt.doubleValue + - [[NSDate date] timeIntervalSince1970]; + if (timeoutSeconds <= 0) { + completion([NSError errorWithDomain:PushyErrorDomain + code:-1 + userInfo:@{ + NSLocalizedDescriptionKey: @"download deadline expired before start", + PushyErrorCodeKey: PushyCode(pushy::error_codes::kDownloadFailed), + }]); + return; } + } + [RCTPushyDownloader download:updateUrl + savePath:zipFilePath + timeoutInterval:timeoutSeconds + progressHandler:^(long long receivedBytes, long long totalBytes) { + PushyReportDownloadProgress(hash, receivedBytes, totalBytes); } completionHandler:^(NSString *path, NSError *error) { if (error != nil) { completion(error); @@ -1403,12 +1503,14 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { RCTLogInfo(@"RCTPushy -- native check: no endpoint reachable, giving up until next launch"); return; } + long long responseAtSeconds = (long long)[[NSDate date] timeIntervalSince1970]; flowjson::Value decision = updateflow::HandleCheckResponse( PushyToStdString(responseText), identity, false, config.Get("afterDownload").AsString()); if (decision.Get("action").AsString() != "download") { - [self persistResponseCache:responseText request:body config:configJson]; + [self persistResponseCache:responseText request:body config:configJson + responseAt:responseAtSeconds]; RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", decision.Get("reason").AsString().c_str()); return; @@ -1429,7 +1531,8 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { originHash:currentVersion]; } if (!downloaded) { - [self persistResponseCache:responseText request:body config:configJson]; + [self persistResponseCache:responseText request:body config:configJson + responseAt:responseAtSeconds]; return; } @@ -1459,14 +1562,16 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { } else { RCTLogInfo(@"RCTPushy -- native check: downloaded %@, activation left to JS", hash); } - [self persistResponseCache:responseText request:body config:configJson]; + [self persistResponseCache:responseText request:body config:configJson + responseAt:responseAtSeconds]; } + (void)persistResponseCache:(NSString *)responseText request:(NSString *)requestBody - config:(NSString *)configJson { + config:(NSString *)configJson + responseAt:(long long)responseAtSeconds { NSDictionary *cacheEntry = @{ - @"ts": @((long long)[[NSDate date] timeIntervalSince1970]), + @"ts": @(responseAtSeconds), @"body": responseText, @"request": requestBody, @"config": configJson, @@ -1494,7 +1599,7 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config for (const auto &endpoint : ordered.elements()) { NSString *base = PushyNormalizeEndpointBase( PushyFromStdString(endpoint.AsString())); - if (base.length == 0) { + if (base.length == 0 || [tried containsObject:base]) { continue; } if (httpAttempts++ >= maxHttpAttempts) { @@ -1551,6 +1656,8 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts hash:(NSString *)hash originHash:(NSString *)originHash { RCTPushy *engine = [self engine]; + NSDate *incrementalDeadline = [NSDate dateWithTimeIntervalSinceNow:600]; + NSDate *fullDeadline = nil; for (const auto &attempt : attempts.elements()) { const std::string &type = attempt.Get("type").AsString(); PushyType pushyType = type == "diff" ? PushyTypePatchFromPpk @@ -1559,13 +1666,30 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts if (pushyType == PushyTypePatchFromPpk && originHash.length == 0) { continue; // diff patches from the running version; none running } + BOOL isFullAttempt = pushyType == PushyTypeFullDownload; + if (isFullAttempt && fullDeadline == nil) { + // diff/pdiff cannot starve the last-resort full download. + fullDeadline = [NSDate dateWithTimeIntervalSinceNow:600]; + } + NSDate *deadline = isFullAttempt ? fullDeadline : incrementalDeadline; for (const auto &urlValue : attempt.Get("urls").elements()) { NSString *url = PushyFromStdString(urlValue.AsString()); if (url == nil) { continue; } + NSTimeInterval remaining = [deadline timeIntervalSinceNow]; + if (remaining <= 0) { + if (isFullAttempt) { + return NO; + } + break; + } NSMutableDictionary *options = - [@{@"updateUrl": url, @"hash": hash} mutableCopy]; + [@{ + @"updateUrl": url, + @"hash": hash, + @"deadlineAt": @([[NSDate date] timeIntervalSince1970] + remaining), + } mutableCopy]; if (pushyType == PushyTypePatchFromPpk) { options[@"originHash"] = originHash; } @@ -1575,12 +1699,13 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts resultError = error; dispatch_semaphore_signal(sem); }]; - // Backstop only — the downloader and patch pipeline carry their - // own timeouts/failures. if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(600 * NSEC_PER_SEC))) != 0) { + (int64_t)(remaining * NSEC_PER_SEC))) != 0) { RCTLogWarn(@"RCTPushy -- native check: %s attempt timed out", type.c_str()); - return NO; + if (isFullAttempt) { + return NO; + } + break; } if (resultError == nil) { return YES; diff --git a/ios/RCTPushy/RCTPushyDownloader.h b/ios/RCTPushy/RCTPushyDownloader.h index 6089e7c6..8a97a415 100644 --- a/ios/RCTPushy/RCTPushyDownloader.h +++ b/ios/RCTPushy/RCTPushyDownloader.h @@ -3,6 +3,7 @@ @interface RCTPushyDownloader : NSObject + (void)download:(NSString *)downloadPath savePath:(NSString *)savePath + timeoutInterval:(NSTimeInterval)timeoutInterval progressHandler:(void (^)(long long, long long))progressHandler completionHandler:(void (^)(NSString *path, NSError *error))completionHandler; diff --git a/ios/RCTPushy/RCTPushyDownloader.mm b/ios/RCTPushy/RCTPushyDownloader.mm index 8b5de4c0..0234fd7c 100644 --- a/ios/RCTPushy/RCTPushyDownloader.mm +++ b/ios/RCTPushy/RCTPushyDownloader.mm @@ -17,6 +17,7 @@ @interface RCTPushyDownloader() @implementation RCTPushyDownloader + (void)download:(NSString *)downloadPath savePath:(NSString *)savePath +timeoutInterval:(NSTimeInterval)timeoutInterval progressHandler:(void (^)(long long receivedBytes, long long totalBytes))progressHandler completionHandler:(void (^)(NSString *path, NSError *error))completionHandler { @@ -28,10 +29,10 @@ + (void)download:(NSString *)downloadPath savePath:(NSString *)savePath downloader.completionHandler = completionHandler; downloader.savePath = savePath; - [downloader startDownload:downloadPath]; + [downloader startDownload:downloadPath timeoutInterval:timeoutInterval]; } -- (void)startDownload:(NSString *)path +- (void)startDownload:(NSString *)path timeoutInterval:(NSTimeInterval)timeoutInterval { NSURL *url = [NSURL URLWithString:path]; if (url == nil) { @@ -50,7 +51,7 @@ - (void)startDownload:(NSString *)path // Android's 10min callTimeout — 300s made a 30MB full package on a slow // (<100KB/s) network fail on iOS while succeeding on Android. sessionConfig.timeoutIntervalForRequest = 30; - sessionConfig.timeoutIntervalForResource = 600; + sessionConfig.timeoutIntervalForResource = MAX(1, timeoutInterval); self.session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 5894e5a5..752d2ec1 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -843,6 +843,40 @@ describe('downloadUpdate fallback chain', () => { expect(downloadPatchFromPpk).toHaveBeenCalledTimes(1); }); + test('reports a release response with no downloadable artifact', async () => { + const { + downloadPatchFromPpk, + downloadPatchFromPackage, + downloadFullUpdate, + } = setupDownloadMocks(); + const logger = mock(() => {}); + const { Pushy, sharedState } = await importFreshClient('dl-no-artifact'); + sharedState.downloadedHash = undefined; + const client = new Pushy({ + appKey: 'demo-app', + logger, + disableTelemetry: true, + }); + + expect( + await client.downloadUpdate({ ...updateInfo, paths: [] }) + ).toBeUndefined(); + await Promise.resolve(); + + expect(downloadPatchFromPpk).not.toHaveBeenCalled(); + expect(downloadPatchFromPackage).not.toHaveBeenCalled(); + expect(downloadFullUpdate).not.toHaveBeenCalled(); + expect(logger).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'errorUpdate', + data: expect.objectContaining({ + newVersion: 'new-hash', + message: 'update response contains no downloadable artifact', + }), + }) + ); + }); + test('adds computed progress to download progress callbacks', async () => { let progressListener: | ((data: { hash: string; received: number; total: number }) => void) @@ -1470,6 +1504,43 @@ describe('syncNativeConfig', () => { resolvers[1](); }); + test('persists a revert to the last synced value while a newer write is in flight', async () => { + const resolvers: Array<() => void> = []; + const syncNativeConfig = mock( + () => + new Promise((resolve) => { + resolvers.push(resolve); + }) + ); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-revert-race'); + const client = new Pushy({ appKey: 'demo-app' }); + + // Finish the initial alert-strategy value (A). + resolvers[0](); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + // Start B, then revert to A before B finishes. A must remain pending even + // though it still equals the last completed value at this instant. + client.setOptions({ updateStrategy: 'silentAndNow' }); + client.setOptions({ updateStrategy: 'alwaysAlert' }); + expect(syncNativeConfig).toHaveBeenCalledTimes(2); + + resolvers[1](); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + expect(syncNativeConfig).toHaveBeenCalledTimes(3); + const reverted = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(reverted.afterDownload).toBe('none'); + resolvers[2](); + }); + test('retries a config write that failed', async () => { let calls = 0; const syncNativeConfig = mock(() => { @@ -1644,6 +1715,32 @@ describe('native check cache reuse', () => { expect(await client.checkUpdate()).toEqual(cachedResult); }); + test('undefined request extras omitted by JSON do not prevent cache reuse', async () => { + const cachedResult = { upToDate: true }; + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 10, + body: JSON.stringify(cachedResult), + request: expectedRequestBody, + config: configJson, + }) + ) + ); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); + const { Pushy } = await importFreshClient('native-cache-undefined-extra'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate({ omitted: undefined })).toEqual( + cachedResult + ); + }); + test('a cache for a different native config is never reused', async () => { const getNativeCheckCache = mock(() => Promise.resolve( diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index d49433ff..98cab94c 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -40,6 +40,7 @@ const createClient = (options: Record = {}) => { async (): Promise => ({ ...updateResult }) ), notifyAfterCheckUpdate: mock(() => {}), + report: mock(() => {}), markSuccess: mock(() => {}), switchVersion: mock(async () => {}), switchVersionLater: mock(async () => {}), @@ -128,6 +129,23 @@ describe('UpdateProvider rendering', () => { expect(client.switchVersion).toHaveBeenCalledWith('next-hash'); }); + test('a hash-less update is reported but never shown as an actionable alert', async () => { + const client = createClient({ updateStrategy: 'alwaysAlert' }); + client.checkUpdate.mockImplementation(async () => ({ + update: true, + name: 'broken rollout entry', + })); + + await renderProvider(client); + + expect(client.report).toHaveBeenCalledWith({ + type: 'errorUpdate', + message: 'update response is missing a version hash', + }); + expect(client.downloadUpdate).not.toHaveBeenCalled(); + expect(mockAlert).not.toHaveBeenCalled(); + }); + test('silentAndNow strategy downloads and switches without alerts', async () => { const client = createClient({ updateStrategy: 'silentAndNow' }); await renderProvider(client); diff --git a/src/client.ts b/src/client.ts index 1921b2d3..9e5e542f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -270,8 +270,8 @@ export class Pushy { this.syncNativeConfig(); }; - /** Serialize the subset of options used by the native cold-start check. */ - private getNativeConfigJson = (): string | undefined => { + /** Build the subset of options used by the native cold-start check. */ + private getNativeConfig = (): Record | undefined => { if ( Platform.OS === 'web' || typeof PushyModule.syncNativeConfig !== 'function' @@ -284,7 +284,7 @@ export class Pushy { if (!appKey || !server?.main?.length) { return undefined; } - return JSON.stringify({ + return { appKey, packageVersion: this.getEffectivePackageVersion(), endpoints: server.main, @@ -298,7 +298,12 @@ export class Pushy { : 'none', rnu: cInfo.rnu, rn: cInfo.rn, - }); + }; + }; + + private getNativeConfigJson = (): string | undefined => { + const config = this.getNativeConfig(); + return config ? JSON.stringify(config) : undefined; }; private flushNativeConfig = () => { @@ -317,7 +322,6 @@ export class Pushy { } catch (e: any) { this.nativeConfigSyncInFlight = false; log('syncNativeConfig failed:', e?.message || e); - this.flushNativeConfig(); return; } syncResult @@ -335,9 +339,13 @@ export class Pushy { private syncNativeConfig = () => { const configJson = this.getNativeConfigJson(); - if (!configJson || configJson === this.syncedNativeConfigJson) { + if (!configJson) { return; } + // Always record the latest desired value, even when it matches the last + // completed write. Example: A synced -> B in flight -> options revert to + // A. Comparing only with synced(A) would drop the revert and leave native + // storage at B after that in-flight write completes. // Coalesce rapid setOptions calls, but serialize bridge writes so an older // completion can never overwrite the newest desired configuration. this.pendingNativeConfigJson = configJson; @@ -348,7 +356,7 @@ export class Pushy { getEffectivePackageVersion = () => this.options.overridePackageVersion || packageVersion; - private jsonStringsEqual = (left: string, right: string): boolean => { + private jsonValuesEqual = (left: unknown, right: unknown): boolean => { const compare = (a: unknown, b: unknown): boolean => { if (a === b) { return true; @@ -371,8 +379,14 @@ export class Pushy { } const leftObject = a as Record; const rightObject = b as Record; - const leftKeys = Object.keys(leftObject).sort(); - const rightKeys = Object.keys(rightObject).sort(); + // Match JSON.stringify semantics for request extras: object properties + // whose value is undefined are omitted from the wire fingerprint. + const leftKeys = Object.keys(leftObject) + .filter((key) => leftObject[key] !== undefined) + .sort(); + const rightKeys = Object.keys(rightObject) + .filter((key) => rightObject[key] !== undefined) + .sort(); return ( leftKeys.length === rightKeys.length && leftKeys.every( @@ -383,11 +397,7 @@ export class Pushy { ); }; - try { - return compare(JSON.parse(left), JSON.parse(right)); - } catch { - return false; - } + return compare(left, right); }; private providerMounted = false; @@ -712,7 +722,7 @@ export class Pushy { * failure falls through to a normal network check. */ private readNativeCheckCache = async ( - requestBody: string + requestBody: Record ): Promise => { try { if (__DEV__ || typeof PushyModule.getNativeCheckCache !== 'function') { @@ -723,15 +733,21 @@ export class Pushy { return undefined; } const entry = JSON.parse(raw); - const configJson = this.getNativeConfigJson(); + const config = this.getNativeConfig(); + const cachedRequest = + typeof entry?.request === 'string' + ? JSON.parse(entry.request) + : undefined; + const cachedConfig = + typeof entry?.config === 'string' + ? JSON.parse(entry.config) + : undefined; if ( typeof entry?.ts !== 'number' || typeof entry?.body !== 'string' || - typeof entry?.request !== 'string' || - !this.jsonStringsEqual(entry.request, requestBody) || - !configJson || - typeof entry?.config !== 'string' || - !this.jsonStringsEqual(entry.config, configJson) + !this.jsonValuesEqual(cachedRequest, requestBody) || + !config || + !this.jsonValuesEqual(cachedConfig, config) ) { return undefined; } @@ -905,7 +921,11 @@ export class Pushy { // the promise so no await lands between the dedup window above and the // lastRespJson assignment below (the JS2-1 double-send lesson). const respJsonPromise = (async (): Promise => { - const cached = await this.readNativeCheckCache(stringifyBody); + // While bundleHash prefetch is still pending, the JS request omits + // that key whereas the native request always includes its synchronously + // computed value. That narrow first-launch window intentionally misses + // the cache rather than delaying checkUpdate for hashing. + const cached = await this.readNativeCheckCache(fetchBody); return cached ?? (await this.fetchCheckResult(fetchPayload)); })(); this.lastRespJson = respJsonPromise; @@ -960,6 +980,16 @@ export class Pushy { log(`current hash ${currentVersion}, ignored`); } else if (decision.reason === 'rolledBack') { log(`rolledback hash ${rolledBackVersion}, ignored`); + } else if (decision.reason === 'noArtifact') { + // A server response that advertises an update but provides no usable + // artifact is a bad release signal, not an ordinary no-update result. + // Keep the user flow silent, but restore the diagnostic/telemetry event + // that the old empty-attempt path emitted. + this.report({ + type: 'errorUpdate', + data: { newVersion: updateInfo.hash || '' }, + message: 'update response contains no downloadable artifact', + }); } return; } diff --git a/src/provider.tsx b/src/provider.tsx index 145ddfa9..8b66b445 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -223,7 +223,7 @@ export const UpdateProvider = ({ // known updateInfo instead of overwriting it with an empty object. return; } - const info = resolveCheckResult( + let info = resolveCheckResult( rootInfo, { packageVersion: client.getEffectivePackageVersion(), @@ -232,6 +232,19 @@ export const UpdateProvider = ({ }, log ); + if ( + info.update && + (typeof info.hash !== 'string' || info.hash.length === 0) + ) { + // A malformed rollout/root entry must not produce an alert whose + // confirm button can never download anything. Surface it to the + // developer telemetry/logger and present it to the app as no update. + client.report({ + type: 'errorUpdate', + message: 'update response is missing a version hash', + }); + info = { upToDate: true }; + } if (info.update) { info.description = info.description ?? ''; } From 60f5fd2334eeedaee385839330370557a534197a Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 10 Aug 2026 08:31:48 +0800 Subject: [PATCH 22/29] docs: record the third review round's open items The 13 closures in 283dfd4 all verified; nine second-order items remain, concentrated in the unified deadline model (joiners inheriting the orchestrator's nearly-exhausted budget, Harmony awaits escaping the budget entirely, wall-clock vs monotonic anchors), plus telemetry inflation, the artifact-less dead-alert variant, deferred-download UX, duplicated progress events and a duplicated completion predicate. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index 573b9f5f..a216758f 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -29,6 +29,46 @@ 77 项 flow core ASan/UBSan、29 项 patch core、Android Release Java 编译、 iOS Release simulator 构建均通过。 +--- + +## 2026-08-10 第三轮评审(283dfd4)开放项 + +上表 13 项的修复经复评确认全部属实;以下为修复自身引入/暴露的新开放项 +(详情与逐条修法见评审面板)。 + +**P2(三端时限模型的二阶问题,建议一并收口)** +1. **iOS 合流者继承 owner 剩余预算**:JS 同 hash 同类型合流到编排器下载时, + 共享会话带的是编排器所剩阶段预算(可能只剩几十秒),其超时会结算全部 + 合流者——修改前 JS 独立下载固定 600s 的不变量对合流路径失效。 +2. **Harmony await 无外层时限**:统一预算只管住 HTTP 流;解压/hpatch 卡死 + 或串行链被长任务占用时 `await context.downloadX` 永不返回,救援轮次挂满 + 进程生命周期,响应缓存也不落盘。修法:performAttempts 层对每个 await 包 + Promise.race 绝对 deadline。 +3. **壁钟 vs 单调钟**:iOS/Harmony 的 deadline 锚壁钟(epoch/Date.now), + Android 锚 nanoTime——首次联网触发 NTP 前跳(恰是救援检测时刻)会让 + iOS/Harmony 全部预算(含 full 保底)瞬时过期。修法:iOS 用 + systemUptime/mach 时基,Harmony 用相对定时器。 + +**P3** +4. **Android 按字面 "full" 判保底预算**,分发 else 分支却把任意非 diff/pdiff + 类型当 full 下载;iOS/Harmony 用排除法——三端对"谁享有 full 保底"不再 + 一致,Android 改排除法即齐。 +5. **noArtifact 遥测重复膨胀**:映射到 download_fail 聚合且每次 + downloadUpdate 调用都上报(静默策略下每个检查周期一条),需按 hash + 会话内去重或换不入聚合的事件类型。 +6. **有 hash 无产物的死弹窗变体**:provider 只降级了无 hash 的 update; + 有 hash 但无任何产物 URL 的坏发布仍弹确认框、确认后静默。 +7. **iOS deferred 的 UX 与陈旧 deadline**:异类型 deferred 请求无进度、 + 延迟开始(UI 冻结);重启时携带编排器的旧 deadlineAt,排队期间过期则 + 瞬时失败并连带结算 JS 合流者。JS 发起的请求不应携带编排器 deadline。 + +**P4** +8. **iOS 合流者进度块重复发事件**(N 合流者 = N 倍 RN 事件,事件发送应只由 + owner 承担)。 +9. **完成判定(bundle+marker)在 iOS 两处手写**,抽 PushyHasCompletedVersion + helper 防漂移。 + + --- ## P1 — Android/Harmony 重复下载竞态,失败路径可删除已安装版本 From 33dc6cb4fc9ff0f15f8bd6de8de121bfccb1d8ea Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 10 Aug 2026 08:49:11 +0800 Subject: [PATCH 23/29] fix: resolve third native check review follow-ups --- NATIVE_CHECK_FOLLOWUPS.md | 20 +++- .../update/NativeCheckOrchestrator.java | 2 +- harmony/pushy/src/main/ets/DownloadTask.ts | 11 ++- .../pushy/src/main/ets/DownloadTaskParams.ts | 6 +- harmony/pushy/src/main/ets/MonotonicClock.ts | 7 ++ .../src/main/ets/NativeCheckOrchestrator.ts | 46 +++++++-- harmony/pushy/src/main/ets/UpdateContext.ts | 12 +-- ios/RCTPushy/RCTPushy.mm | 94 +++++++++++++------ src/__tests__/client.test.ts | 4 + src/__tests__/provider.render.test.tsx | 27 +++++- src/client.ts | 30 ++++-- src/provider.tsx | 20 +++- 12 files changed, 209 insertions(+), 70 deletions(-) create mode 100644 harmony/pushy/src/main/ets/MonotonicClock.ts diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index a216758f..ffd54888 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -25,9 +25,9 @@ | P4 endpoint 斜杠重复 | 已修复 | 三端首轮请求也先查规范化后的 `tried` 集合,重复项不计入 8 次上限 | | P4 Harmony 整请求超时 | 已修复 | check HTTP 增加 15s whole-call cap;更新下载增加绝对 deadline 并在超时后销毁请求 | -代码验证基线:JS 完整回归 172 项、Biome/TypeScript/Harmony strict 类型检查、 -77 项 flow core ASan/UBSan、29 项 patch core、Android Release Java 编译、 -iOS Release simulator 构建均通过。 +代码验证基线:JS 完整回归 173 项、Biome/TypeScript/Harmony strict 类型检查、 +77 项 flow core ASan/UBSan、29 项 patch core、Harmony debug HAR、Android +Release Java 编译、iOS Release simulator 静态库构建均通过。 --- @@ -36,6 +36,20 @@ iOS Release simulator 构建均通过。 上表 13 项的修复经复评确认全部属实;以下为修复自身引入/暴露的新开放项 (详情与逐条修法见评审面板)。 +复核后处理结论: + +| 项 | 结论 | 落地方式 | +|---|---|---| +| 1 iOS joiner 预算 | 已修复 | 注册表记录 owner 的单调时钟 deadline;预算更长的 waiter 观察当前进度但 deferred,owner 成功时由完成标记立即命中,失败时以自己的完整预算重启 | +| 2 Harmony 外层时限 | 已修复 | `performAttempts` 用绝对单调 deadline 包住排队、HTTP、解压和 hpatch 的完整 Promise;底层串行任务即使晚结束也不再阻止编排器落响应缓存 | +| 3 壁钟 deadline | 已修复并修正文档结论 | iOS 改用 `systemUptime`,Harmony 改用 `systemDateTime.getUptime`;full 预算进入 full 阶段才创建,因此原文“增量阶段校时会同时耗尽尚未创建的 full 预算”不成立 | +| 4 Android full 判定 | 已修复 | 与分发逻辑及 iOS/Harmony 一致,非 diff/pdiff 统一视为 full;上游当前只生成三种合法类型,此项属于防御性收口 | +| 5 坏发布遥测膨胀 | 已修复 | 缺 hash 与 noArtifact 共用按 appKey/reason/hash 的进程内去重,保留一次服务端可见的坏发布信号 | +| 6 有 hash 无产物弹窗 | 已修复 | Provider 在展示/静默下载前复用 `decideDownload`,noArtifact 降级为 `upToDate` 与一次开发者遥测 | +| 7 deferred UX/deadline | 已修复 | deferred waiter 订阅当前同 hash 进度;旧 deadline 在重新注册前校验,过期的编排器请求不会成为 owner 或结算后来的 JS 请求 | +| 8 owner-only 进度事件 | 不采纳 | 支持路径在 JS 已按 hash 维持单一原生监听;常见 join 是无监听器的冷启动 engine + JS bridge。限制为 owner 发事件会在 engine 先成为 owner 时丢失 JS 进度 | +| 9 iOS 完成判定重复 | 已修复 | 抽取 `PushyHasCompletedVersionAtPath`,预检与冷启动编排器共用同一 bundle+marker 判定 | + **P2(三端时限模型的二阶问题,建议一并收口)** 1. **iOS 合流者继承 owner 剩余预算**:JS 同 hash 同类型合流到编排器下载时, 共享会话带的是编排器所剩阶段预算(可能只剩几十秒),其超时会结算全部 diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java index bb1ae965..5869e9fa 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -366,7 +366,7 @@ private static boolean performAttempts( // diff patches from the running version; none is running. continue; } - final boolean isFullAttempt = "full".equals(type); + final boolean isFullAttempt = !"diff".equals(type) && !"pdiff".equals(type); if (isFullAttempt && fullDeadlineNanos == 0) { // Incremental failures must not consume the last-resort full // download's budget. Each phase gets one bounded 10min window. diff --git a/harmony/pushy/src/main/ets/DownloadTask.ts b/harmony/pushy/src/main/ets/DownloadTask.ts index 4bdf462e..016501de 100644 --- a/harmony/pushy/src/main/ets/DownloadTask.ts +++ b/harmony/pushy/src/main/ets/DownloadTask.ts @@ -11,6 +11,7 @@ import NativePatchCore, { ARCHIVE_PATCH_TYPE_FROM_PPK, CopyGroupResult, } from './NativePatchCore'; +import { monotonicNowMs } from './MonotonicClock'; export const VERSION_COMPLETE_FILE_NAME = '.pushy-complete'; @@ -346,10 +347,10 @@ export class DownloadTask { let writeQueue = Promise.resolve(); let lastReportedPercentage = -1; let lastReportedBytes = 0; - const deadlineAtMs = params.deadlineAtMs > 0 - ? params.deadlineAtMs - : Date.now() + DOWNLOAD_CALL_TIMEOUT_MS; - if (deadlineAtMs <= Date.now()) { + const deadlineUptimeMs = params.deadlineUptimeMs > 0 + ? params.deadlineUptimeMs + : monotonicNowMs() + DOWNLOAD_CALL_TIMEOUT_MS; + if (deadlineUptimeMs <= monotonicNowMs()) { throw Error('Download deadline expired before start'); } @@ -495,7 +496,7 @@ export class DownloadTask { const deadlinePromise = new Promise((_, reject) => { deadlineTimer = setTimeout(() => { reject(Error('Download exceeded its whole-call deadline')); - }, Math.max(1, deadlineAtMs - Date.now())); + }, Math.max(1, deadlineUptimeMs - monotonicNowMs())); }); const responseCode = await Promise.race([ httpRequest.requestInStream(params.url, { diff --git a/harmony/pushy/src/main/ets/DownloadTaskParams.ts b/harmony/pushy/src/main/ets/DownloadTaskParams.ts index 6494d6dd..ff612466 100644 --- a/harmony/pushy/src/main/ets/DownloadTaskParams.ts +++ b/harmony/pushy/src/main/ets/DownloadTaskParams.ts @@ -16,7 +16,7 @@ export class DownloadTaskParams { targetFile: string = ''; // 目标文件路径 unzipDirectory: string = ''; // 解压目录路径 originDirectory: string = ''; // 原始文件目录路径 - // Native cold-start orchestrator's absolute wall-clock deadline. Zero uses - // the normal public download API's 10-minute whole-call cap. - deadlineAtMs: number = 0; + // Native cold-start orchestrator's absolute monotonic-uptime deadline. Zero + // uses the normal public download API's 10-minute whole-call cap. + deadlineUptimeMs: number = 0; } diff --git a/harmony/pushy/src/main/ets/MonotonicClock.ts b/harmony/pushy/src/main/ets/MonotonicClock.ts new file mode 100644 index 00000000..640ce139 --- /dev/null +++ b/harmony/pushy/src/main/ets/MonotonicClock.ts @@ -0,0 +1,7 @@ +import { systemDateTime } from '@kit.BasicServicesKit'; + +// Absolute deadlines must not use Date.now(): automatic time synchronization +// can move the wall clock while the cold-start rescue round is running. +export function monotonicNowMs(): number { + return systemDateTime.getUptime(systemDateTime.TimeType.STARTUP); +} diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index a94dcf2a..074d85e3 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -4,6 +4,7 @@ import logger from './Logger'; import NativePatchCore from './NativePatchCore'; import type { UpdateContext } from './UpdateContext'; import { isSafePathComponent } from './PathUtils'; +import { monotonicNowMs } from './MonotonicClock'; // 原生冷启动检测(NATIVE_CHECKUPDATE_DESIGN §10):每进程一次,getBundleUrl // 后延迟数秒运行,完全不依赖 app bundle——坏热更把 JS 砸挂后,下次启动仍能 @@ -409,13 +410,37 @@ function normalizeEndpointBase(base: string): string { return base.replace(/\/+$/, ''); } +async function runWithinDeadline( + start: () => Promise, + deadlineUptimeMs: number, +): Promise { + const remainingMs = deadlineUptimeMs - monotonicNowMs(); + if (remainingMs <= 0) { + throw Error('Download phase deadline expired before start'); + } + let deadlineTimer = 0; + const deadlinePromise = new Promise((_, reject) => { + deadlineTimer = setTimeout(() => { + reject(Error('Download phase deadline exceeded')); + }, remainingMs); + }); + try { + // This bounds queueing, HTTP, decompression and native hpatch work from + // the orchestrator's perspective. The serialized task may still finish + // later, but it can no longer prevent the response cache from settling. + await Promise.race([start(), deadlinePromise]); + } finally { + clearTimeout(deadlineTimer); + } +} + async function performAttempts( context: UpdateContext, attempts: DecisionAttempt[], hash: string, originHash: string, ): Promise { - const incrementalDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS; + const incrementalDeadline = monotonicNowMs() + DOWNLOAD_PHASE_TIMEOUT_MS; let fullDeadline = 0; for (const attempt of attempts) { const type = attempt.type ?? ''; @@ -428,14 +453,14 @@ async function performAttempts( if (isFullAttempt && fullDeadline === 0) { // Preserve a full 10min rescue budget even when diff/pdiff exhausted // their own phase window. - fullDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS; + fullDeadline = monotonicNowMs() + DOWNLOAD_PHASE_TIMEOUT_MS; } const deadline = isFullAttempt ? fullDeadline : incrementalDeadline; for (const url of attempt.urls ?? []) { if (!url) { continue; } - if (Date.now() >= deadline) { + if (monotonicNowMs() >= deadline) { if (isFullAttempt) { return false; } @@ -443,11 +468,20 @@ async function performAttempts( } try { if (type === DOWNLOAD_TYPE_DIFF) { - await context.downloadPatchFromPpk(url, hash, originHash, deadline); + await runWithinDeadline( + () => context.downloadPatchFromPpk(url, hash, originHash, deadline), + deadline, + ); } else if (type === DOWNLOAD_TYPE_PDIFF) { - await context.downloadPatchFromPackage(url, hash, deadline); + await runWithinDeadline( + () => context.downloadPatchFromPackage(url, hash, deadline), + deadline, + ); } else { - await context.downloadFullUpdate(url, hash, deadline); + await runWithinDeadline( + () => context.downloadFullUpdate(url, hash, deadline), + deadline, + ); } return true; } catch (e) { diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index 6489f15b..bfabf80a 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -467,7 +467,7 @@ export class UpdateContext { public async downloadFullUpdate( url: string, hash: string, - deadlineAtMs: number = 0, + deadlineUptimeMs: number = 0, ): Promise { try { const params = this.createTaskParams( @@ -477,7 +477,7 @@ export class UpdateContext { ); params.targetFile = `${this.rootDir}/${hash}.ppk`; params.unzipDirectory = `${this.rootDir}/${hash}`; - params.deadlineAtMs = deadlineAtMs; + params.deadlineUptimeMs = deadlineUptimeMs; await this.executeTask(params); } catch (e) { console.error('Failed to download full update:', e); @@ -503,7 +503,7 @@ export class UpdateContext { url: string, hash: string, originHash: string, - deadlineAtMs: number = 0, + deadlineUptimeMs: number = 0, ): Promise { const params = this.createTaskParams( DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK, @@ -514,14 +514,14 @@ export class UpdateContext { params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`; params.unzipDirectory = `${this.rootDir}/${hash}`; params.originDirectory = `${this.rootDir}/${params.originHash}`; - params.deadlineAtMs = deadlineAtMs; + params.deadlineUptimeMs = deadlineUptimeMs; await this.executeTask(params); } public async downloadPatchFromPackage( url: string, hash: string, - deadlineAtMs: number = 0, + deadlineUptimeMs: number = 0, ): Promise { try { const params = this.createTaskParams( @@ -531,7 +531,7 @@ export class UpdateContext { ); params.targetFile = `${this.rootDir}/${hash}.app.patch`; params.unzipDirectory = `${this.rootDir}/${hash}`; - params.deadlineAtMs = deadlineAtMs; + params.deadlineUptimeMs = deadlineUptimeMs; return await this.executeTask(params); } catch (e) { console.error('Failed to download package patch:', e); diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 87475900..3aa7f8aa 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -71,6 +71,26 @@ static NSString * const PARAM_PROGRESS_RECEIVED = @"received"; static NSString * const PARAM_PROGRESS_TOTAL = @"total"; +static NSTimeInterval PushyMonotonicNow(void) { + return [NSProcessInfo processInfo].systemUptime; +} + +static BOOL PushyHasCompletedVersionAtPath(NSString *versionDir) { + NSString *bundlePath = [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]; + NSString *markerPath = [versionDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; + return [[NSFileManager defaultManager] fileExistsAtPath:bundlePath] + && [[NSFileManager defaultManager] fileExistsAtPath:markerPath]; +} + +static NSError *PushyDownloadDeadlineExpiredError(void) { + return [NSError errorWithDomain:PushyErrorDomain + code:-1 + userInfo:@{ + NSLocalizedDescriptionKey: @"download deadline expired before start", + PushyErrorCodeKey: PushyCode(pushy::error_codes::kDownloadFailed), + }]; +} + typedef NS_ENUM(NSInteger, PushyType) { PushyTypeFullDownload = 1, @@ -109,6 +129,7 @@ typedef NS_ENUM(NSInteger, PushyDownloadRegistration) { static PushyDownloadRegistration PushyRegisterDownload( NSString *hash, NSInteger type, + NSTimeInterval deadlineUptime, void (^callback)(NSError *), void (^progress)(long long, long long), void (^deferredStart)(void) @@ -118,6 +139,18 @@ static PushyDownloadRegistration PushyRegisterDownload( NSMutableDictionary *entry = downloads[hash]; if (entry != nil) { if ([entry[@"type"] integerValue] == type) { + NSTimeInterval ownerDeadline = [entry[@"deadlineUptime"] doubleValue]; + // A caller with more time remaining must not inherit an + // owner's shorter timeout. Let it observe the current + // transfer, then restart after the owner settles; the + // completion-marker preflight makes a successful owner free. + if (deadlineUptime > ownerDeadline) { + if (progress != nil) { + [entry[@"progress"] addObject:[progress copy]]; + } + [entry[@"deferred"] addObject:[deferredStart copy]]; + return PushyDownloadRegistrationDeferred; + } [entry[@"callbacks"] addObject:[callback copy]]; if (progress != nil) { [entry[@"progress"] addObject:[progress copy]]; @@ -127,6 +160,12 @@ static PushyDownloadRegistration PushyRegisterDownload( // A diff failure must not settle a joined full request. Queue the // different artifact type behind the owner; once restarted it // registers normally (and re-checks the completion marker). + if (progress != nil) { + // The artifact type differs, but it still installs the same + // target hash. Keep the waiting JS UI moving while its own + // transfer is queued behind the current owner. + [entry[@"progress"] addObject:[progress copy]]; + } [entry[@"deferred"] addObject:[deferredStart copy]]; return PushyDownloadRegistrationDeferred; } @@ -136,6 +175,7 @@ static PushyDownloadRegistration PushyRegisterDownload( } downloads[hash] = [@{ @"type": @(type), + @"deadlineUptime": @(deadlineUptime), @"callbacks": [NSMutableArray arrayWithObject:[callback copy]], @"progress": progressHandlers, @"deferred": [NSMutableArray array], @@ -914,14 +954,21 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( } NSString *unzipDir = [dir stringByAppendingPathComponent:hash]; - NSString *completedBundle = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]; - NSString *completedMarker = [unzipDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; - if ([[NSFileManager defaultManager] fileExistsAtPath:completedBundle] - && [[NSFileManager defaultManager] fileExistsAtPath:completedMarker]) { + if (PushyHasCompletedVersionAtPath(unzipDir)) { callback(nil); return; } + NSTimeInterval deadlineUptime = PushyMonotonicNow() + 600; + NSNumber *configuredDeadline = options[@"deadlineUptime"]; + if ([configuredDeadline isKindOfClass:[NSNumber class]]) { + deadlineUptime = configuredDeadline.doubleValue; + } + if (deadlineUptime <= PushyMonotonicNow()) { + callback(PushyDownloadDeadlineExpiredError()); + return; + } + void (^progress)(long long, long long) = ^(long long receivedBytes, long long totalBytes) { if (self->hasListeners) { [self sendEventWithName:EVENT_PROGRESS_DOWNLOAD body:@{ @@ -935,7 +982,7 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( [self performUpdate:type options:options callback:callback]; }; PushyDownloadRegistration registration = PushyRegisterDownload( - hash, type, callback, progress, deferredStart); + hash, type, deadlineUptime, callback, progress, deferredStart); if (registration != PushyDownloadRegistrationOwner) { RCTLogInfo( @"RCTPushy -- %@ in-flight download for %@", @@ -975,20 +1022,10 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( }; RCTLogInfo(@"RCTPushy -- download file %@", updateUrl); - NSTimeInterval timeoutSeconds = 600; - NSNumber *deadlineAt = options[@"deadlineAt"]; - if ([deadlineAt isKindOfClass:[NSNumber class]]) { - timeoutSeconds = deadlineAt.doubleValue - - [[NSDate date] timeIntervalSince1970]; - if (timeoutSeconds <= 0) { - completion([NSError errorWithDomain:PushyErrorDomain - code:-1 - userInfo:@{ - NSLocalizedDescriptionKey: @"download deadline expired before start", - PushyErrorCodeKey: PushyCode(pushy::error_codes::kDownloadFailed), - }]); - return; - } + NSTimeInterval timeoutSeconds = deadlineUptime - PushyMonotonicNow(); + if (timeoutSeconds <= 0) { + completion(PushyDownloadDeadlineExpiredError()); + return; } [RCTPushyDownloader download:updateUrl savePath:zipFilePath @@ -1521,10 +1558,7 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { } NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash]; - NSString *bundlePath = [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]; - NSString *completePath = [versionDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; - BOOL downloaded = [[NSFileManager defaultManager] fileExistsAtPath:bundlePath] - && [[NSFileManager defaultManager] fileExistsAtPath:completePath]; + BOOL downloaded = PushyHasCompletedVersionAtPath(versionDir); if (!downloaded) { downloaded = [self performAttempts:decision.Get("attempts") hash:hash @@ -1656,8 +1690,8 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts hash:(NSString *)hash originHash:(NSString *)originHash { RCTPushy *engine = [self engine]; - NSDate *incrementalDeadline = [NSDate dateWithTimeIntervalSinceNow:600]; - NSDate *fullDeadline = nil; + NSTimeInterval incrementalDeadline = PushyMonotonicNow() + 600; + NSTimeInterval fullDeadline = 0; for (const auto &attempt : attempts.elements()) { const std::string &type = attempt.Get("type").AsString(); PushyType pushyType = type == "diff" ? PushyTypePatchFromPpk @@ -1667,17 +1701,17 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts continue; // diff patches from the running version; none running } BOOL isFullAttempt = pushyType == PushyTypeFullDownload; - if (isFullAttempt && fullDeadline == nil) { + if (isFullAttempt && fullDeadline == 0) { // diff/pdiff cannot starve the last-resort full download. - fullDeadline = [NSDate dateWithTimeIntervalSinceNow:600]; + fullDeadline = PushyMonotonicNow() + 600; } - NSDate *deadline = isFullAttempt ? fullDeadline : incrementalDeadline; + NSTimeInterval deadline = isFullAttempt ? fullDeadline : incrementalDeadline; for (const auto &urlValue : attempt.Get("urls").elements()) { NSString *url = PushyFromStdString(urlValue.AsString()); if (url == nil) { continue; } - NSTimeInterval remaining = [deadline timeIntervalSinceNow]; + NSTimeInterval remaining = deadline - PushyMonotonicNow(); if (remaining <= 0) { if (isFullAttempt) { return NO; @@ -1688,7 +1722,7 @@ + (BOOL)performAttempts:(const flowjson::Value &)attempts [@{ @"updateUrl": url, @"hash": hash, - @"deadlineAt": @([[NSDate date] timeIntervalSince1970] + remaining), + @"deadlineUptime": @(deadline), } mutableCopy]; if (pushyType == PushyTypePatchFromPpk) { options[@"originHash"] = originHash; diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 752d2ec1..8e50a881 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -858,6 +858,9 @@ describe('downloadUpdate fallback chain', () => { disableTelemetry: true, }); + expect( + await client.downloadUpdate({ ...updateInfo, paths: [] }) + ).toBeUndefined(); expect( await client.downloadUpdate({ ...updateInfo, paths: [] }) ).toBeUndefined(); @@ -875,6 +878,7 @@ describe('downloadUpdate fallback chain', () => { }), }) ); + expect(logger).toHaveBeenCalledTimes(1); }); test('adds computed progress to download progress callbacks', async () => { diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index 98cab94c..7ae79fa3 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -21,6 +21,8 @@ const updateResult: CheckResult = { name: '1.0.1', hash: 'next-hash', description: 'bugfix', + full: 'next.ppk', + paths: ['https://cdn.example.com'], }; const createClient = (options: Record = {}) => { @@ -41,6 +43,7 @@ const createClient = (options: Record = {}) => { ), notifyAfterCheckUpdate: mock(() => {}), report: mock(() => {}), + reportInvalidUpdateOnce: mock(() => {}), markSuccess: mock(() => {}), switchVersion: mock(async () => {}), switchVersionLater: mock(async () => {}), @@ -138,10 +141,26 @@ describe('UpdateProvider rendering', () => { await renderProvider(client); - expect(client.report).toHaveBeenCalledWith({ - type: 'errorUpdate', - message: 'update response is missing a version hash', - }); + expect(client.reportInvalidUpdateOnce).toHaveBeenCalledWith('missingHash'); + expect(client.downloadUpdate).not.toHaveBeenCalled(); + expect(mockAlert).not.toHaveBeenCalled(); + }); + + test('an update without a downloadable artifact is never shown as actionable', async () => { + const client = createClient({ updateStrategy: 'alwaysAlert' }); + client.checkUpdate.mockImplementation(async () => ({ + update: true, + hash: 'broken-artifact-hash', + name: 'broken release', + paths: [], + })); + + await renderProvider(client); + + expect(client.reportInvalidUpdateOnce).toHaveBeenCalledWith( + 'noArtifact', + 'broken-artifact-hash' + ); expect(client.downloadUpdate).not.toHaveBeenCalled(); expect(mockAlert).not.toHaveBeenCalled(); }); diff --git a/src/client.ts b/src/client.ts index 9e5e542f..e4317cfe 100644 --- a/src/client.ts +++ b/src/client.ts @@ -169,6 +169,7 @@ export class Pushy { private syncedNativeConfigJson?: string; private pendingNativeConfigJson?: string; private nativeConfigSyncInFlight = false; + private reportedInvalidUpdates = new Set(); version = cInfo.rnu; loggerPromise = (() => { @@ -430,6 +431,25 @@ export class Pushy { return i18n.t(key as any, values); }; + reportInvalidUpdateOnce = ( + reason: 'missingHash' | 'noArtifact', + hash = '' + ) => { + const key = `${this.options.appKey}:${reason}:${hash}`; + if (this.reportedInvalidUpdates.has(key)) { + return; + } + this.reportedInvalidUpdates.add(key); + this.report({ + type: 'errorUpdate', + message: + reason === 'missingHash' + ? 'update response is missing a version hash' + : 'update response contains no downloadable artifact', + ...(hash ? { data: { newVersion: hash } } : {}), + }); + }; + report = async ({ type, message = '', @@ -983,13 +1003,9 @@ export class Pushy { } else if (decision.reason === 'noArtifact') { // A server response that advertises an update but provides no usable // artifact is a bad release signal, not an ordinary no-update result. - // Keep the user flow silent, but restore the diagnostic/telemetry event - // that the old empty-attempt path emitted. - this.report({ - type: 'errorUpdate', - data: { newVersion: updateInfo.hash || '' }, - message: 'update response contains no downloadable artifact', - }); + // Keep the user flow silent and report at most once per bad release in + // this process: repeated checks must not inflate download_fail health. + this.reportInvalidUpdateOnce('noArtifact', updateInfo.hash || ''); } return; } diff --git a/src/provider.tsx b/src/provider.tsx index 8b66b445..2b92f3c5 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -23,9 +23,10 @@ import { currentVersionInfo, getCurrentVersionInfo, packageVersion, + rolledBackVersion, } from './core'; import type { CheckResult, ProgressData, UpdateTestPayload } from './type'; -import { resolveCheckResult } from './updateFlowCore'; +import { decideDownload, resolveCheckResult } from './updateFlowCore'; import { assertWeb, log, noop } from './utils'; export const UpdateProvider = ({ @@ -239,12 +240,21 @@ export const UpdateProvider = ({ // A malformed rollout/root entry must not produce an alert whose // confirm button can never download anything. Surface it to the // developer telemetry/logger and present it to the app as no update. - client.report({ - type: 'errorUpdate', - message: 'update response is missing a version hash', - }); + client.reportInvalidUpdateOnce('missingHash'); info = { upToDate: true }; } + if (info.update && !info.expired) { + const decision = decideDownload( + info, + { currentVersion, rolledBackVersion }, + __DEV__ + ); + if (decision.action === 'none' && decision.reason === 'noArtifact') { + // Do not show an alert whose confirm action can only return silently. + client.reportInvalidUpdateOnce('noArtifact', info.hash || ''); + info = { upToDate: true }; + } + } if (info.update) { info.description = info.description ?? ''; } From 845de2010c722282b00a2af0ada1864cca2b14dd Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 11 Aug 2026 08:05:41 +0800 Subject: [PATCH 24/29] fix: address remaining native check review comments --- NATIVE_CHECK_FOLLOWUPS.md | 4 ++-- src/__tests__/client.test.ts | 17 +++++++++++++++++ src/__tests__/provider.render.test.tsx | 17 +++++++++++++++++ src/client.ts | 13 +++++++++++-- src/provider.tsx | 1 + 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index ffd54888..a16cd4ec 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -125,8 +125,8 @@ Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已 **复评补充(181952a 引入/暴露)——下载轮次时限应作为一个整体设计统一三端**: 1. 整轮 600s deadline 只落了 Android;iOS 仍逐 URL 600s(上界 - attempts×urls×600s,可达 90 分钟),Harmony 完全没有(慢滴 CDN 每 - <60s 一字节即可绕过下载任务的不活动看门狗,轮次可跑数小时); + attempts×urls×600s,可达 90 分钟),Harmony 完全没有(慢滴 CDN 在每个 + 60 秒窗口内返回至少一个字节即可绕过下载任务的不活动看门狗,轮次可跑数小时); 2. Android 的 deadline 在下一个下载**已发起后**才检查——超时轮次仍会 多启动一个孤儿下载,浪费带宽且可能与 JS 重试并发写版本文件 (修法:发起前先查); diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 8e50a881..8612f6da 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -1451,6 +1451,23 @@ describe('syncNativeConfig', () => { expect(config.afterDownload).toBe('setNeedUpdate'); }); + test('persists an explicit disabled state when the config becomes invalid', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-disabled'); + const client = new Pushy({ appKey: 'demo-app' }); + + client.setOptions({ appKey: '' }); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + expect(syncNativeConfig).toHaveBeenCalledTimes(2); + expect( + JSON.parse((syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0]) + ).toEqual({ disabled: true }); + }); + test('persists and exposes the effective overridden package version', async () => { const syncNativeConfig = mock(() => Promise.resolve()); setupClientMocks({ syncNativeConfig }); diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index 7ae79fa3..3a4a7da7 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -146,6 +146,23 @@ describe('UpdateProvider rendering', () => { expect(mockAlert).not.toHaveBeenCalled(); }); + test('an expired app package without a bundle hash keeps its download action', async () => { + const client = createClient({ updateStrategy: 'alwaysAlert' }); + client.checkUpdate.mockImplementation(async () => ({ + expired: true, + update: true, + downloadUrl: 'https://cdn.example.com/app-release.apk', + })); + + await renderProvider(client); + + expect(client.reportInvalidUpdateOnce).not.toHaveBeenCalled(); + expect(mockAlert).toHaveBeenCalledTimes(1); + const [, , buttons] = mockAlert.mock.calls[0] as any[]; + expect(buttons).toHaveLength(1); + expect(buttons[0].text).toBe('alert_update_button'); + }); + test('an update without a downloadable artifact is never shown as actionable', async () => { const client = createClient({ updateStrategy: 'alwaysAlert' }); client.checkUpdate.mockImplementation(async () => ({ diff --git a/src/client.ts b/src/client.ts index e4317cfe..fed29dec 100644 --- a/src/client.ts +++ b/src/client.ts @@ -96,6 +96,11 @@ const cloneServerConfig = (server: UpdateServerConfig): UpdateServerConfig => ({ queryUrls: server.queryUrls ? [...server.queryUrls] : undefined, }); +// Persist an object (rather than an empty string) so every native bridge keeps +// accepting the payload while the orchestrators treat the missing appKey and +// endpoints as an explicit disabled state. +const NATIVE_CONFIG_DISABLED_JSON = '{"disabled":true}'; + const excludeConfiguredEndpoints = ( endpoints: string[], configuredEndpoints: string[] @@ -339,10 +344,14 @@ export class Pushy { }; private syncNativeConfig = () => { - const configJson = this.getNativeConfigJson(); - if (!configJson) { + if ( + Platform.OS === 'web' || + typeof PushyModule.syncNativeConfig !== 'function' + ) { return; } + const configJson = + this.getNativeConfigJson() ?? NATIVE_CONFIG_DISABLED_JSON; // Always record the latest desired value, even when it matches the last // completed write. Example: A synced -> B in flight -> options revert to // A. Comparing only with synced(A) would drop the revert and leave native diff --git a/src/provider.tsx b/src/provider.tsx index 2b92f3c5..72c4f9d4 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -234,6 +234,7 @@ export const UpdateProvider = ({ log ); if ( + !info.expired && info.update && (typeof info.hash !== 'string' || info.hash.length === 0) ) { From 0f4651eaaea8b373819a62a098354a6ca9e18699 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 11 Aug 2026 09:17:46 +0800 Subject: [PATCH 25/29] fix: address fourth native check review --- .../reactnative/modules/update/DownloadTask.java | 6 +++++- .../modules/update/UpdateContext.java | 13 ++++++++++++- ios/RCTPushy/RCTPushy.mm | 4 ++++ src/__tests__/provider.render.test.tsx | 16 ++++++++++++++++ src/__tests__/setup.ts | 2 +- src/provider.tsx | 9 ++++++--- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index c88a1bd9..2c14b9ed 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -115,8 +115,12 @@ private void downloadFile() throws IOException { if (remainingNanos <= 0) { throw new IOException("Download deadline expired before start"); } + long remainingMillis = Math.max( + 1L, + java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(remainingNanos) + ); requestClient = HTTP_CLIENT.newBuilder() - .callTimeout(remainingNanos, java.util.concurrent.TimeUnit.NANOSECONDS) + .callTimeout(remainingMillis, java.util.concurrent.TimeUnit.MILLISECONDS) .build(); } diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index 83ea8a09..cf806111 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -376,10 +376,21 @@ public void switchVersion(String hash) { if (!isSafePathComponent(hash)) { throw new IllegalArgumentException("Invalid hash: " + hash); } - if (!new File(rootDir, hash+"/index.bundlejs").exists()) { + File versionDir = new File(rootDir, hash); + File bundleFile = new File(versionDir, "index.bundlejs"); + if (!bundleFile.isFile()) { throw new IllegalStateException("Bundle version " + hash + " not found."); } StateCoreResult currentState = getStateSnapshot(); + boolean isLegacyActivatedVersion = hash.equals(currentState.currentVersion) + || hash.equals(currentState.lastVersion); + if (!new File(versionDir, VERSION_COMPLETE_FILE).isFile() + && !isLegacyActivatedVersion) { + // Versions activated before completion markers were introduced are + // explicitly grandfathered through current/last state. An arbitrary + // markerless directory may be a crash-left partial install. + throw new IllegalStateException("Bundle version " + hash + " is incomplete."); + } StateCoreResult nextState = runStateCore( STATE_OP_SWITCH_VERSION, currentState, diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 3aa7f8aa..81ccde49 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -1534,6 +1534,10 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { std::string bodyJson = flowjson::Stringify(updateflow::BuildCheckRequestBody(input)); NSString *body = [NSString stringWithUTF8String:bodyJson.c_str()]; + if (body == nil) { + RCTLogWarn(@"RCTPushy -- native check: request body is not valid UTF-8"); + return; + } NSString *responseText = [self runCheckRequest:config appKey:appKey body:body]; if (responseText == nil) { diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index 3a4a7da7..17c17ce6 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -182,6 +182,22 @@ describe('UpdateProvider rendering', () => { expect(mockAlert).not.toHaveBeenCalled(); }); + test('a rolled-back update is never shown as actionable', async () => { + const client = createClient({ updateStrategy: 'alwaysAlert' }); + client.checkUpdate.mockImplementation(async () => ({ + update: true, + hash: 'rolled-back-hash', + full: 'rolled-back-hash.ppk', + paths: ['https://cdn.example.com'], + })); + + await renderProvider(client); + + expect(client.reportInvalidUpdateOnce).not.toHaveBeenCalled(); + expect(client.downloadUpdate).not.toHaveBeenCalled(); + expect(mockAlert).not.toHaveBeenCalled(); + }); + test('silentAndNow strategy downloads and switches without alerts', async () => { const client = createClient({ updateStrategy: 'silentAndNow' }); await renderProvider(client); diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts index 79d5e2b0..f0514101 100644 --- a/src/__tests__/setup.ts +++ b/src/__tests__/setup.ts @@ -55,7 +55,7 @@ const installBaseMocks = () => { packageVersion: '1.0.0', currentVersion: 'hash', isFirstTime: false, - rolledBackVersion: '', + rolledBackVersion: 'rolled-back-hash', buildTime: '2023-01-01', uuid: 'uuid', setLocalHashInfo: () => {}, diff --git a/src/provider.tsx b/src/provider.tsx index 72c4f9d4..d11a9fdc 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -250,9 +250,12 @@ export const UpdateProvider = ({ { currentVersion, rolledBackVersion }, __DEV__ ); - if (decision.action === 'none' && decision.reason === 'noArtifact') { - // Do not show an alert whose confirm action can only return silently. - client.reportInvalidUpdateOnce('noArtifact', info.hash || ''); + if (decision.action === 'none') { + if (decision.reason === 'noArtifact') { + // Invalid server data is worth reporting; local rollout guards are + // expected no-ops and stay silent. + client.reportInvalidUpdateOnce('noArtifact', info.hash || ''); + } info = { upToDate: true }; } } From 5f5d0cb9bce25b67a46ef1780ffdf0f9ad3db4ca Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:29:13 +0000 Subject: [PATCH 26/29] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit --- src/__tests__/provider.render.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index 17c17ce6..2fbc8c63 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -193,6 +193,7 @@ describe('UpdateProvider rendering', () => { await renderProvider(client); + expect(client.checkUpdate).toHaveBeenCalled(); expect(client.reportInvalidUpdateOnce).not.toHaveBeenCalled(); expect(client.downloadUpdate).not.toHaveBeenCalled(); expect(mockAlert).not.toHaveBeenCalled(); From ffe5da3d88cd1c2fd76d949e5106f831299305fc Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 11 Aug 2026 18:12:21 +0800 Subject: [PATCH 27/29] docs: record the fourth review round and the merge-gate verdict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The third round's nine closures all verified. Ten items remain, three of them merge-relevant: the native check ignoring checkStrategy and undoing resetToPackagedBundle (the root cause of the now-red e2e-ios, confirmed across two runs failing different tests — a race signature — while master is green), the strict deadline comparison that makes the iOS same-type join path unreachable, and Harmony's STARTUP clock counting deep sleep where the other two platforms stop. The rest are follow-ups. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index a16cd4ec..04b35225 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -259,6 +259,68 @@ update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不 --- +## 2026-08-11 第四轮评审(60f5fd2..5f5d0cb)开放项 + +上一轮 9 项闭环全部属实(第 8 项"owner-only 进度事件"维护者明确不采纳,理由 +成立)。以下为仍开放项;**前三条建议合入前处理**,其余可随小版本。 + +### 合并阻断 + +1. **原生检测无视 `checkStrategy`,并能撞销 `resetToPackagedBundle`** + (CI 已红)。`getNativeConfig` 只用 `updateStrategy` 折算 `afterDownload`, + 从不读 `checkStrategy`;三端也没有 reset↔检测 的任何联动(无 generation、 + 无取消,reset 也不清 `nativeCheckResp`)。e2e app 明写 `checkStrategy: null` + 却仍被原生自动下载+激活;`resetToPackagedBundle` 可被在飞的检测撤销。 + **证据**:e2e-ios 在 `0f4651e` 与 `5f5d0cb` 两次运行均挂在 `beforeEach` + 的 `bundleLabel: BINARY_BASE`,且两次挂的是不同用例(竞态签名);失败态 + `currentHash: e2e-full-v1` 恰是"reset 后从零检查会拿到的第一个版本"; + master 上 e2e-ios 为绿 → 本分支引入的回归。本分支新增的 + `hasCompletedVersion` 快路径(版本已落盘则跳过下载直接 switchVersion) + 把竞态窗口从"下载完"压到"5 秒后瞬间",是这轮才炸的原因。 + **修法**:`afterDownload` 计算纳入 `checkStrategy`(为 null 时降为 + `'none'`——只下载不激活,`forceBoot` 仍可救砖);`resetToPackagedBundle` + bump 进程级 generation,编排器在 `switchVersion`/落缓存前比对,变了就 + 放弃,并清掉响应缓存。 + +2. **iOS 同类型 join 分支实际不可达**。`deadlineUptime > ownerDeadline` 是 + 严格大于,而 JS 发起的请求总晚于 owner 的计算时刻,因此永远走 deferred: + P1"一个 hash 共享一次下载"的意图失效,退化为串行重下;CDN 黑洞时用户 + 弹窗可转近 20 分钟(改前是合流后一次失败并回退下一候选 URL)。修法:比较 + 加容忍阈值,或 JS 发起的请求不参与 deadline 比较。 + +3. **Harmony 用 `TimeType.STARTUP`**(计深度睡眠),而 iOS `systemUptime` / + Android `nanoTime` 睡眠时停走——"三端统一单调钟"不成立。锁屏休眠数分钟 + 即让预算过期、救援中止,同网络的另两端能续传完成。一行改 + `TimeType.ACTIVE`。 + +### 后续小版本 + +4. Harmony 超时只放弃编排器的 `await`,卡死的 DownloadTask 仍占 `taskChain`, + 后续 attempt 只是排队并空烧自己的新鲜预算(有效 full 产物 + 完整预算都在 + 却仍救不回)。修法:超时真正取消底层任务,或救援轮次用独立链。 +5. iOS/Harmony 的 `switchVersion` 仍可激活半完成安装(Android 本轮加了无标记 + 拒绝守卫,三端 parity 分歧)。 +6. iOS deferred 异类型 waiter 订阅了 owner 的进度流,收到的是另一种产物的 + 字节数(进度条先到 100% 再回 0%)。修法:只向同类型合流者广播。 +7. 缺 hash 守卫新加的 `!info.expired` 让 `{expired:true, update:true}` 且无 + hash 的畸形响应既不降级也不上报,却仍把 `update:true` 发给业务侧。 +8. `getNativeConfigJson() ?? '{"disabled":true}'` 会把瞬时为空的 + `appKey`/`server.main` 变成持久 disabled 写入,覆盖上一份可用配置并长期 + 关掉救砖能力。修法:仅在确实不可用(web/旧原生)时写 disabled。 +9. `noArtifact` 上报位置从 `client.downloadUpdate`(实际尝试下载)移到 + `provider.checkUpdate`(只要检查就发),映射到服务端 `download_fail` 聚合 + 后会把一次坏发布放大成全量设备级的健康下降。 +10. 两处 cleanup:Android `switchVersion` 重写了同文件已有的 + `hasCompletedVersion` 谓词;provider 为一个是非问题重跑完整 + `decideDownload`(分配 URL 计划后丢弃)。 + +### 累计 + +四轮共 40 条发现:27 修复、2 显式接受、1 不采纳、10 开放(其中 3 条建议合入 +前处理)。 + +--- + ## 发版清单(非代码缺陷,勿遗漏) - [ ] e2e:坏 bundle → 原生拉修复版 → 下次启动复活的端到端用例 From 41918dc0dc3e4ebf27a29e0e8d3f270c91c38836 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 11 Aug 2026 18:17:38 +0800 Subject: [PATCH 28/29] fix: stop the native check from overriding checkStrategy and resets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three merge-gate fixes from the fourth review round. The cold-start check ignored checkStrategy entirely and had no interaction with resetToPackagedBundle, so an app that had switched automatic checks off still got silent version switches, and a reset could be undone by a check that was already in flight — the root cause of the red e2e-ios, where the beforeEach reset lost its BINARY_BASE assertion to a check that re-activated the version it had just removed (two runs failed different tests, the signature of a race; master is green). afterDownload now requires checkStrategy != null, so the check still runs and still downloads (a bricked device stays rescuable) but activation waits for JS or for the server's explicit forceBoot. A reset generation counter, mirrored on all three platforms, is sampled before the round starts and compared before activation and before publishing the response cache, so a reset that lands mid-round always wins; the reset also drops the cached response that still advertised the removed version. The iOS registry compared absolute deadlines with a strict >, and a JS caller always computes now+600 a few seconds after the owner did, so the same-type join path was unreachable and every second caller serialized behind the owner. It now compares remaining budget and defers only a genuinely starved owner (less than half the newcomer's budget left). Harmony's monotonic clock used TimeType.STARTUP, which counts deep sleep, while iOS systemUptime and Android nanoTime both stop — a device sleeping mid-download lost its budget and aborted a rescue the other platforms complete. Switched to TimeType.ACTIVE. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 23 ++++++++- .../update/NativeCheckOrchestrator.java | 20 ++++++-- .../modules/update/UpdateContext.java | 15 ++++++ harmony/pushy/src/main/ets/MonotonicClock.ts | 8 +++- .../src/main/ets/NativeCheckOrchestrator.ts | 40 ++++++++++------ harmony/pushy/src/main/ets/UpdateContext.ts | 16 +++++++ ios/RCTPushy/RCTPushy.mm | 48 +++++++++++++++---- src/__tests__/client.test.ts | 36 ++++++++++++++ src/client.ts | 12 ++++- 9 files changed, 187 insertions(+), 31 deletions(-) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index 04b35225..a9bbbe26 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -264,7 +264,9 @@ update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不 上一轮 9 项闭环全部属实(第 8 项"owner-only 进度事件"维护者明确不采纳,理由 成立)。以下为仍开放项;**前三条建议合入前处理**,其余可随小版本。 -### 合并阻断 +### 合并阻断 —— 已于 2026-08-11 修复(见下方"修复说明") + +#### 原始问题描述 1. **原生检测无视 `checkStrategy`,并能撞销 `resetToPackagedBundle`** (CI 已红)。`getNativeConfig` 只用 `updateStrategy` 折算 `afterDownload`, @@ -293,6 +295,25 @@ update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不 即让预算过期、救援中止,同网络的另两端能续传完成。一行改 `TimeType.ACTIVE`。 +#### 修复说明(2026-08-11) + +1. **checkStrategy + reset 竞态**:`getNativeConfig` 的 `afterDownload` 现在 + 要求 `checkStrategy != null` —— 关掉自动检查的应用不会再被塞一次它没要 + 过的版本切换;检测本身照跑(救砖能力不变),`forceBoot` 仍可激活。另加 + **reset 代数守卫**:`resetToPackagedBundle` 递增进程级计数并清掉响应缓存, + 编排器在开跑前采样、在激活与落缓存前比对,不一致即整轮丢弃。三端同构 + (iOS `std::atomic`、Android `AtomicLong`、Harmony 静态计数)。 +2. **iOS join 分支不可达**:比较从"绝对 deadline 严格大于"改为"剩余预算", + 仅当 owner 剩余不足新来者的一半时才 defer —— 正常情况(JS 晚几秒发起) + 恢复合流,只有真正濒临耗尽的 owner 才让位。 +3. **Harmony 单调钟**:`TimeType.STARTUP` → `TimeType.ACTIVE`,与 iOS + `systemUptime` / Android `nanoTime` 的"睡眠时停走"语义对齐。 + +验证:JS 178 项(新增 2 项 checkStrategy 折算用例)、Biome/tsc/DevEco +strict、77 金标向量 + ASan/UBSan、`.so` 符号与 16KB 对齐、iOS clang +(DEBUG=0/1)、Android javac(main+oldarch)。本轮不触 `cpp/`,无需重生成 +向量或重编 `.so`。 + ### 后续小版本 4. Harmony 超时只放弃编排器的 `await`,卡死的 DownloadTask 仍占 `taskChain`, diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java index 5869e9fa..60ac9332 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -67,6 +67,9 @@ private static void runOnce( UpdateContext context, String launchRolledBackVersion ) throws JSONException { + // Sampled before any IO: a reset landing while this round runs must + // win over the round's decision. + final long resetGeneration = UpdateContext.getResetGeneration(); String configJson = context.getKv(KEY_CONFIG); if (configJson == null || configJson.isEmpty()) { // No persisted config (old integration / first ever launch): the @@ -155,8 +158,10 @@ private static void runOnce( } JSONObject decision = new JSONObject(decisionJson); if (!"download".equals(decision.optString("action"))) { - persistResponseCache( - context, configJson, body, responseText, responseAtSeconds); + if (UpdateContext.getResetGeneration() == resetGeneration) { + persistResponseCache( + context, configJson, body, responseText, responseAtSeconds); + } Log.i(UpdateContext.TAG, "native check: nothing to do (" + decision.optString("reason") + ")"); return; @@ -174,8 +179,10 @@ private static void runOnce( if (!downloaded) { // The native attempt has finished, so JS may safely reuse the // response and retry through its own strategy chain. - persistResponseCache( - context, configJson, body, responseText, responseAtSeconds); + if (UpdateContext.getResetGeneration() == resetGeneration) { + persistResponseCache( + context, configJson, body, responseText, responseAtSeconds); + } return; } @@ -193,6 +200,11 @@ private static void runOnce( context.setKv("hash_" + hash, hashInfo.toString()); } + if (UpdateContext.getResetGeneration() != resetGeneration) { + Log.i(UpdateContext.TAG, "native check: reset during round, dropping result"); + return; + } + if (decision.optBoolean("activate", false)) { // Silent strategies or a server-marked forceBoot version // (per-version remote override — the brick rescue): activate for diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index cf806111..24503016 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -48,6 +48,13 @@ public class UpdateContext { private static final int STATE_OP_RESOLVE_LAUNCH = 6; private static final String KEY_FIRST_LOAD_MARKED = "firstLoadMarked"; static final String VERSION_COMPLETE_FILE = ".pushy-complete"; + // Bumped by resetToPackagedBundle. The cold-start check runs for minutes + // and may already hold a decision when the app resets to the packaged + // bundle; the orchestrator samples this counter and abandons activation + // (and its response cache) when the value moved, so an in-flight rescue + // can never resurrect the version the app just reset away from. + private static final java.util.concurrent.atomic.AtomicLong resetGeneration = + new java.util.concurrent.atomic.AtomicLong(0); // Singleton instance private static volatile UpdateContext sInstance; @@ -498,6 +505,9 @@ public void resetToPackagedBundle() { } persistEditor(editor, "reset to packaged bundle"); ignoreRollback = false; + // editor.clear() above already dropped the cached check response; it + // still advertised the version this reset removed. + resetGeneration.incrementAndGet(); Log.i(TAG, "Reset to packaged bundle"); DownloadTaskParams params = new DownloadTaskParams(); @@ -632,6 +642,11 @@ public String getBundleUrl(String defaultAssetsUrl) { } } + /** Sampled/compared by the native check orchestrator; see resetGeneration. */ + static long getResetGeneration() { + return resetGeneration.get(); + } + boolean hasCompletedVersion(String hash) { if (!isSafePathComponent(hash)) { return false; diff --git a/harmony/pushy/src/main/ets/MonotonicClock.ts b/harmony/pushy/src/main/ets/MonotonicClock.ts index 640ce139..243958f0 100644 --- a/harmony/pushy/src/main/ets/MonotonicClock.ts +++ b/harmony/pushy/src/main/ets/MonotonicClock.ts @@ -2,6 +2,12 @@ import { systemDateTime } from '@kit.BasicServicesKit'; // Absolute deadlines must not use Date.now(): automatic time synchronization // can move the wall clock while the cold-start rescue round is running. +// +// TimeType.ACTIVE (not STARTUP) is the semantic match for the other two +// platforms: iOS NSProcessInfo.systemUptime and Android System.nanoTime both +// stop during deep sleep, so a device that sleeps mid-download resumes with +// its budget intact. STARTUP keeps counting through sleep and would abort a +// rescue that the same network completes on iOS/Android. export function monotonicNowMs(): number { - return systemDateTime.getUptime(systemDateTime.TimeType.STARTUP); + return systemDateTime.getUptime(systemDateTime.TimeType.ACTIVE); } diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index 074d85e3..9f5fc364 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -109,6 +109,9 @@ async function runOnce( context: UpdateContext, launchRolledBackVersion: string, ): Promise { + // 在任何 IO 之前采样:resetToPackagedBundle 会递增它,本轮运行期间发生的 + // reset 必须赢过本轮的决策。 + const resetGeneration = context.getResetGeneration(); const configJson = context.getKv(KEY_CONFIG); if (!configJson) { // 无落盘配置(老接入/首启):静默不跑——这就是灰度开关。 @@ -184,13 +187,15 @@ async function runOnce( } const decision = JSON.parse(decisionJson) as Decision; if (decision.action !== 'download') { - persistResponseCache( - context, - configJson, - body, - responseText, - responseAtSeconds, - ); + if (context.getResetGeneration() === resetGeneration) { + persistResponseCache( + context, + configJson, + body, + responseText, + responseAtSeconds, + ); + } logger.debug(TAG, `nothing to do (${decision.reason ?? ''})`); return; } @@ -209,13 +214,15 @@ async function runOnce( ); } if (!downloaded) { - persistResponseCache( - context, - configJson, - body, - responseText, - responseAtSeconds, - ); + if (context.getResetGeneration() === resetGeneration) { + persistResponseCache( + context, + configJson, + body, + responseText, + responseAtSeconds, + ); + } return; } @@ -235,6 +242,11 @@ async function runOnce( context.setKv(`hash_${hash}`, JSON.stringify(hashInfo)); } + if (context.getResetGeneration() !== resetGeneration) { + logger.debug(TAG, 'reset during round, dropping result'); + return; + } + if (decision.activate === true) { // 静默策略、或服务端按版本标记的 forceBoot(远程覆盖,救砖指令):激活 // 到下次启动;否则激活权留给 JS(§6)。 diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index bfabf80a..a252e485 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -38,6 +38,10 @@ export class UpdateContext { // resetToPackagedBundle 不能删它的目录:热更包内的图片等资源是运行时按需 // 读盘的,静默(不重启)reset 若删掉会导致后续所有未加载过的资源失败。 private static launchVersion: string = ''; + // 由 resetToPackagedBundle 递增。原生冷启动检测可能跑数分钟并已握有决策, + // 期间发生的 reset 必须赢:编排器采样该值,发现变化即放弃激活与响应缓存, + // 在飞的救援不会把刚被重置掉的版本装回去。 + private static resetGeneration: number = 0; private static cachedPackageVersion: string = ''; private static cachedBuildTime: string = ''; // 单例:确保 bundle provider 与 TurboModule 共用同一份 preferences 内存状态, @@ -447,7 +451,14 @@ export class UpdateContext { } } this.persistState(resetState, { clearFirstLoadMarker: true }); + // 缓存里的响应仍在宣告本次 reset 刚删掉的版本,一并丢弃,避免 JS 侧复用。 + try { + this.preferences.deleteSync('nativeCheckResp'); + } catch (e: any) { + console.error('Failed to clear native check cache on reset:', e); + } UpdateContext.ignoreRollback = false; + UpdateContext.resetGeneration += 1; // maxAgeDays=0:删除下载目录内容,仅保留当前运行版本的目录(残留目录由 // 下次常规清理回收)。挂到串行任务链尾,避免与在飞的解压/打补丁并发。 @@ -464,6 +475,11 @@ export class UpdateContext { this.trace('resetToPackagedBundle:after'); } + /** 供原生检测编排器采样/比对的 reset 代数(见 resetGeneration 注释)。 */ + public getResetGeneration(): number { + return UpdateContext.resetGeneration; + } + public async downloadFullUpdate( url: string, hash: string, diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 81ccde49..1a7bf7ac 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -100,6 +100,12 @@ typedef NS_ENUM(NSInteger, PushyType) { }; static std::atomic ignoreRollback{false}; +// Bumped by resetToPackagedBundle. The cold-start check runs for minutes and +// may already hold a decision when the app resets to the packaged bundle; it +// samples this counter and abandons activation (and its response cache) when +// the value moved, so an in-flight rescue can never resurrect the version the +// app just reset away from. +static std::atomic pushyResetGeneration{0}; // The version whose bundle this process actually loaded (resolved in // +bundleURL). resetToPackagedBundle must not delete its directory: update // assets (images/fonts) are read from it on demand at runtime, so wiping it @@ -140,11 +146,18 @@ static PushyDownloadRegistration PushyRegisterDownload( if (entry != nil) { if ([entry[@"type"] integerValue] == type) { NSTimeInterval ownerDeadline = [entry[@"deadlineUptime"] doubleValue]; - // A caller with more time remaining must not inherit an - // owner's shorter timeout. Let it observe the current - // transfer, then restart after the owner settles; the - // completion-marker preflight makes a successful owner free. - if (deadlineUptime > ownerDeadline) { + // A caller with substantially more time must not inherit an + // owner's nearly-exhausted timeout: it observes the current + // transfer and restarts after the owner settles (the + // completion-marker preflight makes a successful owner free). + // The comparison is on remaining budget, not on the absolute + // deadline: a JS caller always computes now+600 a few seconds + // after the owner did, so a strict `>` would defer every + // second caller and turn the shared download back into + // serialized re-downloads. Only a genuinely starved owner + // (less than half the newcomer's budget left) defers. + const NSTimeInterval now = PushyMonotonicNow(); + if (2 * (ownerDeadline - now) < (deadlineUptime - now)) { if (progress != nil) { [entry[@"progress"] addObject:[progress copy]]; } @@ -861,7 +874,12 @@ - (instancetype)init } [defaults removeObjectForKey:keyFirstLoadMarked]; [defaults removeObjectForKey:KeyPackageUpdatedMarked]; + // A cached response still advertises the version this reset just + // removed; dropping it stops the JS side from reusing that answer. + [defaults removeObjectForKey:keyNativeCheckCache]; ignoreRollback = false; + // Invalidate any cold-start check that is already in flight. + pushyResetGeneration.fetch_add(1); }); dispatch_async(_fileQueue, ^{ @@ -1468,6 +1486,9 @@ + (RCTPushy *)engine { } + (void)runOnce:(NSString *)launchRolledBackVersion { + // Sampled before any IO: resetToPackagedBundle bumps it, and a reset that + // lands while this round is running must win over the round's decision. + const uint64_t resetGeneration = pushyResetGeneration.load(); NSUserDefaults *defaults = PushyDefaults(); NSString *configJson = [defaults stringForKey:keyNativeConfig]; if (configJson.length == 0) { @@ -1550,8 +1571,10 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { PushyToStdString(responseText), identity, false, config.Get("afterDownload").AsString()); if (decision.Get("action").AsString() != "download") { - [self persistResponseCache:responseText request:body config:configJson - responseAt:responseAtSeconds]; + if (pushyResetGeneration.load() == resetGeneration) { + [self persistResponseCache:responseText request:body config:configJson + responseAt:responseAtSeconds]; + } RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", decision.Get("reason").AsString().c_str()); return; @@ -1569,8 +1592,10 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { originHash:currentVersion]; } if (!downloaded) { - [self persistResponseCache:responseText request:body config:configJson - responseAt:responseAtSeconds]; + if (pushyResetGeneration.load() == resetGeneration) { + [self persistResponseCache:responseText request:body config:configJson + responseAt:responseAtSeconds]; + } return; } @@ -1586,6 +1611,11 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { [defaults setObject:[NSString stringWithUTF8String:flowjson::Stringify(hashInfo).c_str()] forKey:PushyHashInfoKey(hash)]; + if (pushyResetGeneration.load() != resetGeneration) { + RCTLogInfo(@"RCTPushy -- native check: reset during round, dropping result"); + return; + } + if (decision.Get("activate").Truthy()) { // Silent strategies or a server-marked forceBoot version (per-version // remote override — the brick rescue): activate for the next launch. diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 8612f6da..90c8f1c7 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -1423,6 +1423,42 @@ describe('syncNativeConfig', () => { expect(config.packageVersion).toBe('1.0.0'); }); + test('disabling automatic checks keeps activation with JS too', async () => { + // checkStrategy: null means "never check on your own". The native + // cold-start check still runs (that is what rescues a bricked device), + // but it must not hand the app a version switch it never asked for — + // only the server's explicit forceBoot may still activate. + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-no-autocheck'); + new Pushy({ + appKey: 'demo-app', + updateStrategy: 'silentAndNow', + checkStrategy: null, + }); + + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.afterDownload).toBe('none'); + }); + + test('silent strategy with automatic checks still activates', async () => { + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ syncNativeConfig }); + const { Pushy } = await importFreshClient('sync-config-autocheck'); + new Pushy({ + appKey: 'demo-app', + updateStrategy: 'silentAndNow', + checkStrategy: 'onAppStart', + }); + + const config = JSON.parse( + (syncNativeConfig.mock.calls.at(-1) as unknown as string[])[0] + ); + expect(config.afterDownload).toBe('setNeedUpdate'); + }); + test('alert strategies keep activation with JS (afterDownload none)', async () => { const syncNativeConfig = mock(() => Promise.resolve()); setupClientMocks({ syncNativeConfig }); diff --git a/src/client.ts b/src/client.ts index fed29dec..a3edd2ab 100644 --- a/src/client.ts +++ b/src/client.ts @@ -286,10 +286,16 @@ export class Pushy { // and the feature-detect would false-positive. return undefined; } - const { appKey, server, updateStrategy } = this.options; + const { appKey, server, updateStrategy, checkStrategy } = this.options; if (!appKey || !server?.main?.length) { return undefined; } + // An app that turned automatic checks off (checkStrategy: null) must not + // be handed a version switch it never asked for. The cold-start check + // still runs and still downloads — that is what keeps a bricked device + // rescuable — but activation waits for the JS side, or for the server's + // explicit per-version forceBoot directive (shouldActivateAfterDownload). + const autoCheckEnabled = checkStrategy != null; return { appKey, packageVersion: this.getEffectivePackageVersion(), @@ -299,7 +305,9 @@ export class Pushy { // only under the silent strategies; alert-style strategies keep // activation with the JS side (§6/§10.1). afterDownload: - updateStrategy === 'silentAndNow' || updateStrategy === 'silentAndLater' + autoCheckEnabled && + (updateStrategy === 'silentAndNow' || + updateStrategy === 'silentAndLater') ? 'setNeedUpdate' : 'none', rnu: cInfo.rnu, From ffa606f16c647af5a8ad0666d12aa9b78afe8987 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 11 Aug 2026 19:16:50 +0800 Subject: [PATCH 29/29] fix: commit native check results atomically against resets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first version of the reset guard was compare-and-act: the generation was checked, then switchVersion and the response-cache write followed as separate steps, and the hash_ version info was written before the check ran at all. A reset landing inside that window could still be overwritten — the window shrank from minutes to microseconds but never closed. Version info, activation and the response cache now land through one commitNativeCheckResult(expectedGeneration, ...) entry point that re-checks the generation and performs every write under the same mutual exclusion resetToPackagedBundle uses, and the reset invalidates the generation before it starts clearing state. iOS reuses the existing state lock (switchVersion grew a lock-free core so the commit can hold the non-reentrant lock across all three writes), Android shares a commit lock, and Harmony documents why ArkTS's single thread plus an await-free commit is already atomic. All three call sites — nothing to do, download failed, download succeeded — go through the same entry point, so no write escapes the guard. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECK_FOLLOWUPS.md | 15 +- .../update/NativeCheckOrchestrator.java | 65 ++++---- .../modules/update/UpdateContext.java | 45 +++++- .../src/main/ets/NativeCheckOrchestrator.ts | 81 +++++----- harmony/pushy/src/main/ets/UpdateContext.ts | 39 ++++- ios/RCTPushy/RCTPushy.mm | 146 +++++++++++------- 6 files changed, 257 insertions(+), 134 deletions(-) diff --git a/NATIVE_CHECK_FOLLOWUPS.md b/NATIVE_CHECK_FOLLOWUPS.md index a9bbbe26..3524e811 100644 --- a/NATIVE_CHECK_FOLLOWUPS.md +++ b/NATIVE_CHECK_FOLLOWUPS.md @@ -309,10 +309,21 @@ update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不 3. **Harmony 单调钟**:`TimeType.STARTUP` → `TimeType.ACTIVE`,与 iOS `systemUptime` / Android `nanoTime` 的"睡眠时停走"语义对齐。 +**补强(2026-08-11,CodeRabbit 复核后)**:初版守卫是 compare-and-act,且 +`hash_` 元信息写在守卫之前——reset 若落在"比对通过"与"写入"之间仍 +可被覆盖。现改为**一次原子提交**:版本元信息 + 激活 + 响应缓存合并为 +`commitNativeCheckResult(expectedGeneration, ...)`,内部先复核代数再落全部 +写入;`resetToPackagedBundle` 与之互斥并**先失效代数再清状态**。互斥手段按 +端选取:iOS 复用既有 `PushyWithStateLock`(为此把 switchVersion 拆出无锁核 +`PushySwitchVersionLocked`,避免不可重入死锁)、Android 用共享 +`commitLock`、Harmony 是 ArkTS 单线程且提交内无 await,天然原子(已注释说 +明)。三处落点(元信息/激活/缓存)与三个调用点(nothing-to-do、未下载、成功) +全部走同一入口。 + 验证:JS 178 项(新增 2 项 checkStrategy 折算用例)、Biome/tsc/DevEco strict、77 金标向量 + ASan/UBSan、`.so` 符号与 16KB 对齐、iOS clang -(DEBUG=0/1)、Android javac(main+oldarch)。本轮不触 `cpp/`,无需重生成 -向量或重编 `.so`。 +(DEBUG=0/1)、Android javac(main+oldarch)、OHOS 工具链语法。本轮不触 +`cpp/`,无需重生成向量或重编 `.so`。 ### 后续小版本 diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java index 60ac9332..6ab4e38e 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -158,10 +158,9 @@ private static void runOnce( } JSONObject decision = new JSONObject(decisionJson); if (!"download".equals(decision.optString("action"))) { - if (UpdateContext.getResetGeneration() == resetGeneration) { - persistResponseCache( - context, configJson, body, responseText, responseAtSeconds); - } + context.commitNativeCheckResult( + resetGeneration, null, null, false, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds)); Log.i(UpdateContext.TAG, "native check: nothing to do (" + decision.optString("reason") + ")"); return; @@ -179,15 +178,16 @@ private static void runOnce( if (!downloaded) { // The native attempt has finished, so JS may safely reuse the // response and retry through its own strategy chain. - if (UpdateContext.getResetGeneration() == resetGeneration) { - persistResponseCache( - context, configJson, body, responseText, responseAtSeconds); - } + context.commitNativeCheckResult( + resetGeneration, null, null, false, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds)); return; } - // Persist name/description/metaInfo alongside the version, mirroring - // the JS side's setLocalHashInfo after a successful download. + // Version info (mirroring the JS side's setLocalHashInfo), the + // activation and the response cache all land in one atomic commit — + // see UpdateContext.commitNativeCheckResult. + String hashInfoJson = null; JSONObject info = decision.optJSONObject("info"); if (info != null) { JSONObject hashInfo = new JSONObject(); @@ -197,37 +197,36 @@ private static void runOnce( hashInfo.put(key, value); } } - context.setKv("hash_" + hash, hashInfo.toString()); + hashInfoJson = hashInfo.toString(); } - - if (UpdateContext.getResetGeneration() != resetGeneration) { - Log.i(UpdateContext.TAG, "native check: reset during round, dropping result"); + // Silent strategies or a server-marked forceBoot version (per-version + // remote override — the brick rescue) activate for the next launch; + // otherwise activation stays with the JS side. + boolean activate = decision.optBoolean("activate", false); + boolean committed; + try { + committed = context.commitNativeCheckResult( + resetGeneration, + hash, + hashInfoJson, + activate, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds)); + } catch (Exception e) { + Log.w(UpdateContext.TAG, "native check: commit failed: " + e); return; } - - if (decision.optBoolean("activate", false)) { - // Silent strategies or a server-marked forceBoot version - // (per-version remote override — the brick rescue): activate for - // the next launch. Otherwise activation stays with the JS side. - try { - context.switchVersion(hash); - Log.i(UpdateContext.TAG, - "native check: downloaded " + hash + " and set for next launch"); - } catch (Exception e) { - Log.w(UpdateContext.TAG, "native check: switchVersion failed: " + e); - } + if (!committed) { + Log.i(UpdateContext.TAG, "native check: reset during round, dropping result"); + } else if (activate) { + Log.i(UpdateContext.TAG, + "native check: downloaded " + hash + " and set for next launch"); } else { Log.i(UpdateContext.TAG, "native check: downloaded " + hash + ", activation left to JS"); } - // Publish the response only after native file/state work is complete; - // otherwise JS can observe it and start a competing download. - persistResponseCache( - context, configJson, body, responseText, responseAtSeconds); } - private static void persistResponseCache( - UpdateContext context, + private static String buildResponseCacheJson( String configJson, String requestBody, String responseText, @@ -238,7 +237,7 @@ private static void persistResponseCache( cacheEntry.put("body", responseText); cacheEntry.put("request", requestBody); cacheEntry.put("config", configJson); - context.setKv(KEY_RESP_CACHE, cacheEntry.toString()); + return cacheEntry.toString(); } private static final OkHttpClient httpClient = new OkHttpClient.Builder() diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index 24503016..338e5a90 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -55,6 +55,9 @@ public class UpdateContext { // can never resurrect the version the app just reset away from. private static final java.util.concurrent.atomic.AtomicLong resetGeneration = new java.util.concurrent.atomic.AtomicLong(0); + // Held by resetToPackagedBundle and by the cold-start check's commit, so + // the generation check and the writes it guards are one atomic step. + private static final Object commitLock = new Object(); // Singleton instance private static volatile UpdateContext sInstance; @@ -491,6 +494,16 @@ public void clearFirstTime() { * for gray release bucketing and must not change on reset. */ public void resetToPackagedBundle() { + synchronized (commitLock) { + resetToPackagedBundleLocked(); + } + } + + private void resetToPackagedBundleLocked() { + // Invalidate any in-flight cold-start round before clearing state: a + // round committing under the same lock afterwards sees the new + // generation and drops its result. + resetGeneration.incrementAndGet(); StateCoreResult resetState = new StateCoreResult(); resetState.packageVersion = getPackageVersion(); resetState.buildTime = getBuildTime(); @@ -507,7 +520,6 @@ public void resetToPackagedBundle() { ignoreRollback = false; // editor.clear() above already dropped the cached check response; it // still advertised the version this reset removed. - resetGeneration.incrementAndGet(); Log.i(TAG, "Reset to packaged bundle"); DownloadTaskParams params = new DownloadTaskParams(); @@ -647,6 +659,37 @@ static long getResetGeneration() { return resetGeneration.get(); } + /** + * Commit everything a cold-start round persists — version info, the + * activation, the response cache — under one lock that first re-checks the + * reset generation. resetToPackagedBundle takes the same lock, so there is + * no compare-and-act window: either the whole round lands, or the reset + * wins and none of it does. Returns whether the round was committed. + */ + boolean commitNativeCheckResult( + long expectedGeneration, + String hash, + String hashInfoJson, + boolean activate, + String responseCacheJson + ) { + synchronized (commitLock) { + if (resetGeneration.get() != expectedGeneration) { + return false; + } + if (hash != null && hashInfoJson != null) { + setKv("hash_" + hash, hashInfoJson); + } + if (activate && hash != null) { + switchVersion(hash); + } + if (responseCacheJson != null) { + setKv(NativeCheckOrchestrator.KEY_RESP_CACHE, responseCacheJson); + } + return true; + } + } + boolean hasCompletedVersion(String hash) { if (!isSafePathComponent(hash)) { return false; diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index 9f5fc364..63170018 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -187,15 +187,13 @@ async function runOnce( } const decision = JSON.parse(decisionJson) as Decision; if (decision.action !== 'download') { - if (context.getResetGeneration() === resetGeneration) { - persistResponseCache( - context, - configJson, - body, - responseText, - responseAtSeconds, - ); - } + context.commitNativeCheckResult( + resetGeneration, + '', + '', + false, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds), + ); logger.debug(TAG, `nothing to do (${decision.reason ?? ''})`); return; } @@ -214,20 +212,19 @@ async function runOnce( ); } if (!downloaded) { - if (context.getResetGeneration() === resetGeneration) { - persistResponseCache( - context, - configJson, - body, - responseText, - responseAtSeconds, - ); - } + context.commitNativeCheckResult( + resetGeneration, + '', + '', + false, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds), + ); return; } // 与 JS 侧下载成功后的 setLocalHashInfo 对齐,持久化版本元信息。 const info = decision.info; + let hashInfoJson = ''; if (info) { const hashInfo: DecisionInfo = {}; if (typeof info.name === 'string') { @@ -239,50 +236,48 @@ async function runOnce( if (typeof info.metaInfo === 'string') { hashInfo.metaInfo = info.metaInfo; } - context.setKv(`hash_${hash}`, JSON.stringify(hashInfo)); + hashInfoJson = JSON.stringify(hashInfo); } - if (context.getResetGeneration() !== resetGeneration) { - logger.debug(TAG, 'reset during round, dropping result'); + // 版本元信息、激活与响应缓存一次性原子提交(见 commitNativeCheckResult); + // 缓存只在原生文件/状态工作结束后公开,避免 JS 观察到响应后并发下载。 + // 静默策略、或服务端按版本标记的 forceBoot(远程覆盖,救砖指令)才激活。 + const activate = decision.activate === true; + let committed = false; + try { + committed = context.commitNativeCheckResult( + resetGeneration, + hash, + hashInfoJson, + activate, + buildResponseCacheJson(configJson, body, responseText, responseAtSeconds), + ); + } catch (e) { + logger.error(TAG, `commit failed: ${e}`); return; } - - if (decision.activate === true) { - // 静默策略、或服务端按版本标记的 forceBoot(远程覆盖,救砖指令):激活 - // 到下次启动;否则激活权留给 JS(§6)。 - try { - context.switchVersion(hash); - logger.debug(TAG, `downloaded ${hash} and set for next launch`); - } catch (e) { - logger.error(TAG, `switchVersion failed: ${e}`); - } + if (!committed) { + logger.debug(TAG, 'reset during round, dropping result'); + } else if (activate) { + logger.debug(TAG, `downloaded ${hash} and set for next launch`); } else { logger.debug(TAG, `downloaded ${hash}, activation left to JS`); } - // 仅在原生文件/状态工作结束后公开缓存,避免 JS 观察到响应后并发下载。 - persistResponseCache( - context, - configJson, - body, - responseText, - responseAtSeconds, - ); } -function persistResponseCache( - context: UpdateContext, +function buildResponseCacheJson( configJson: string, requestBody: string, responseText: string, responseAtSeconds: number, -): void { +): string { const cacheEntry: RespCacheEntry = { ts: responseAtSeconds, body: responseText, request: requestBody, config: configJson, }; - context.setKv(KEY_RESP_CACHE, JSON.stringify(cacheEntry)); + return JSON.stringify(cacheEntry); } function isValidCheckResponse(responseText: string | undefined): boolean { diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index a252e485..5727e93c 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -9,7 +9,10 @@ import { DownloadTaskParams } from './DownloadTaskParams'; import { bundleManager } from '@kit.AbilityKit'; import { util } from '@kit.ArkTS'; import logger from './Logger'; -import { scheduleNativeCheck } from './NativeCheckOrchestrator'; +import { + KEY_RESP_CACHE, + scheduleNativeCheck, +} from './NativeCheckOrchestrator'; import NativePatchCore, { STATE_OP_CLEAR_FIRST_TIME, STATE_OP_CLEAR_ROLLBACK_MARK, @@ -450,15 +453,17 @@ export class UpdateContext { console.error('Failed to clear hash info on reset:', e); } } + // 先让在飞的原生检测轮次失效,再清理状态:随后在同一(单线程)执行序里 + // 提交的轮次会看到新代数并整轮丢弃。 + UpdateContext.resetGeneration += 1; this.persistState(resetState, { clearFirstLoadMarker: true }); // 缓存里的响应仍在宣告本次 reset 刚删掉的版本,一并丢弃,避免 JS 侧复用。 try { - this.preferences.deleteSync('nativeCheckResp'); + this.preferences.deleteSync(KEY_RESP_CACHE); } catch (e: any) { console.error('Failed to clear native check cache on reset:', e); } UpdateContext.ignoreRollback = false; - UpdateContext.resetGeneration += 1; // maxAgeDays=0:删除下载目录内容,仅保留当前运行版本的目录(残留目录由 // 下次常规清理回收)。挂到串行任务链尾,避免与在飞的解压/打补丁并发。 @@ -480,6 +485,34 @@ export class UpdateContext { return UpdateContext.resetGeneration; } + /** + * 一次性提交原生检测轮次的全部持久化结果(版本元信息、激活、响应缓存): + * 先复核 reset 代数,再落所有写入。ArkTS 单线程 + 本方法内无 await,因此 + * 校验与写入之间不存在可插入 reset 的窗口(iOS/Android 用锁达到同一效果)。 + * 返回是否提交成功。 + */ + public commitNativeCheckResult( + expectedGeneration: number, + hash: string, + hashInfoJson: string, + activate: boolean, + responseCacheJson: string, + ): boolean { + if (UpdateContext.resetGeneration !== expectedGeneration) { + return false; + } + if (hash && hashInfoJson) { + this.setKv(`hash_${hash}`, hashInfoJson); + } + if (activate && hash) { + this.switchVersion(hash); + } + if (responseCacheJson) { + this.setKv(KEY_RESP_CACHE, responseCacheJson); + } + return true; + } + public async downloadFullUpdate( url: string, hash: string, diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 1a7bf7ac..2bf76f68 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -415,6 +415,21 @@ static void PushyApplyStateToDefaults(NSUserDefaults *defaults, const pushy::sta PushyFromStdString(state.rolled_back_version)); } +// Version switch without acquiring the state lock: the caller must already +// hold it. Lets the cold-start check commit its whole result (version info, +// switch, response cache) inside one lock acquisition, so resetToPackagedBundle +// can never interleave between the generation check and the writes. +static void PushySwitchVersionLocked(NSString *hash) { + NSUserDefaults *defaults = PushyDefaults(); + pushy::state::State next = pushy::state::SwitchVersion( + PushyStateFromDefaults(defaults), + PushyToStdString(hash) + ); + PushyApplyStateToDefaults(defaults, next); + // Re-enable first-load consumption and rollback checks for the newly selected bundle. + ignoreRollback = false; +} + @interface RCTPushy () - (void)downloadUpdate:(PushyType)type options:(NSDictionary *)options @@ -457,10 +472,13 @@ + (NSString *)buildTime; @interface RCTPushyOrchestrator : NSObject + (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion; + (void)runOnce:(NSString *)launchRolledBackVersion; -+ (void)persistResponseCache:(NSString *)responseText - request:(NSString *)requestBody - config:(NSString *)configJson - responseAt:(long long)responseAtSeconds; ++ (BOOL)commitRoundWithGeneration:(uint64_t)generation + hashInfo:(NSDictionary *)hashInfoEntry + activate:(NSString *)hashToActivate + responseText:(NSString *)responseText + request:(NSString *)requestBody + config:(NSString *)configJson + responseAt:(long long)responseAtSeconds; @end // Shared by the getBundleHash RCT method and the native cold-start check. @@ -855,6 +873,10 @@ - (instancetype)init // for gray release bucketing and must not change on reset. __block NSString *keepVersion = nil; PushyWithStateLock(^{ + // Invalidate any in-flight cold-start round before clearing state, so + // a round that commits under this same lock afterwards always sees the + // new generation and drops its result. + pushyResetGeneration.fetch_add(1); NSUserDefaults *defaults = PushyDefaults(); keepVersion = pushyLaunchVersion; @@ -878,8 +900,6 @@ - (instancetype)init // removed; dropping it stops the JS side from reusing that answer. [defaults removeObjectForKey:keyNativeCheckCache]; ignoreRollback = false; - // Invalidate any cold-start check that is already in flight. - pushyResetGeneration.fetch_add(1); }); dispatch_async(_fileQueue, ^{ @@ -1215,14 +1235,7 @@ - (BOOL)switchVersion:(NSString *)hash error:(NSError **)error } PushyWithStateLock(^{ - NSUserDefaults *defaults = PushyDefaults(); - pushy::state::State next = pushy::state::SwitchVersion( - PushyStateFromDefaults(defaults), - PushyToStdString(hash) - ); - PushyApplyStateToDefaults(defaults, next); - // Re-enable first-load consumption and rollback checks for the newly selected bundle. - ignoreRollback = false; + PushySwitchVersionLocked(hash); }); return YES; } @@ -1571,10 +1584,13 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { PushyToStdString(responseText), identity, false, config.Get("afterDownload").AsString()); if (decision.Get("action").AsString() != "download") { - if (pushyResetGeneration.load() == resetGeneration) { - [self persistResponseCache:responseText request:body config:configJson - responseAt:responseAtSeconds]; - } + [self commitRoundWithGeneration:resetGeneration + hashInfo:nil + activate:nil + responseText:responseText + request:body + config:configJson + responseAt:responseAtSeconds]; RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", decision.Get("reason").AsString().c_str()); return; @@ -1592,52 +1608,57 @@ + (void)runOnce:(NSString *)launchRolledBackVersion { originHash:currentVersion]; } if (!downloaded) { - if (pushyResetGeneration.load() == resetGeneration) { - [self persistResponseCache:responseText request:body config:configJson - responseAt:responseAtSeconds]; - } + [self commitRoundWithGeneration:resetGeneration + hashInfo:nil + activate:nil + responseText:responseText + request:body + config:configJson + responseAt:responseAtSeconds]; return; } // Persist name/description/metaInfo alongside the version, mirroring the // JS side's setLocalHashInfo after a successful download. const flowjson::Value &info = decision.Get("info"); - flowjson::Value hashInfo = flowjson::Value::Object(); + NSMutableDictionary *versionInfo = [NSMutableDictionary dictionary]; for (const char *key : {"name", "description", "metaInfo"}) { if (info.Get(key).IsString()) { - hashInfo.Set(key, info.Get(key)); + versionInfo[@(key)] = PushyFromStdString(info.Get(key).AsString()) ?: @""; } } - [defaults setObject:[NSString stringWithUTF8String:flowjson::Stringify(hashInfo).c_str()] - forKey:PushyHashInfoKey(hash)]; - - if (pushyResetGeneration.load() != resetGeneration) { + // Silent strategies or a server-marked forceBoot version (per-version + // remote override — the brick rescue) activate for the next launch; + // otherwise activation stays with the JS side (§6/§10.1). + BOOL activate = decision.Get("activate").Truthy(); + BOOL committed = [self commitRoundWithGeneration:resetGeneration + hashInfo:@{@"hash": hash, @"info": versionInfo} + activate:activate ? hash : nil + responseText:responseText + request:body + config:configJson + responseAt:responseAtSeconds]; + if (!committed) { RCTLogInfo(@"RCTPushy -- native check: reset during round, dropping result"); - return; - } - - if (decision.Get("activate").Truthy()) { - // Silent strategies or a server-marked forceBoot version (per-version - // remote override — the brick rescue): activate for the next launch. - // Otherwise activation stays with the JS side (§6/§10.1). - NSError *error = nil; - if ([[self engine] switchVersion:hash error:&error]) { - RCTLogInfo(@"RCTPushy -- native check: downloaded %@ and set for next launch", hash); - } else { - RCTLogWarn(@"RCTPushy -- native check: switchVersion failed: %@", - error.localizedDescription); - } + } else if (activate) { + RCTLogInfo(@"RCTPushy -- native check: downloaded %@ and set for next launch", hash); } else { RCTLogInfo(@"RCTPushy -- native check: downloaded %@, activation left to JS", hash); } - [self persistResponseCache:responseText request:body config:configJson - responseAt:responseAtSeconds]; } -+ (void)persistResponseCache:(NSString *)responseText - request:(NSString *)requestBody - config:(NSString *)configJson - responseAt:(long long)responseAtSeconds { +// Everything a round persists — version info, the activation, the response +// cache — is written inside ONE state-lock acquisition that first re-checks the +// reset generation. resetToPackagedBundle bumps that generation under the same +// lock, so there is no compare-and-act window: either the whole round commits, +// or the reset wins and none of it does. ++ (BOOL)commitRoundWithGeneration:(uint64_t)generation + hashInfo:(NSDictionary *)hashInfoEntry + activate:(NSString *)hashToActivate + responseText:(NSString *)responseText + request:(NSString *)requestBody + config:(NSString *)configJson + responseAt:(long long)responseAtSeconds { NSDictionary *cacheEntry = @{ @"ts": @(responseAtSeconds), @"body": responseText, @@ -1645,10 +1666,31 @@ + (void)persistResponseCache:(NSString *)responseText @"config": configJson, }; NSData *cacheData = [NSJSONSerialization dataWithJSONObject:cacheEntry options:0 error:nil]; - if (cacheData != nil) { - [PushyDefaults() setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding] - forKey:keyNativeCheckCache]; - } + __block BOOL committed = NO; + PushyWithStateLock(^{ + if (pushyResetGeneration.load() != generation) { + return; + } + NSUserDefaults *defaults = PushyDefaults(); + if (hashInfoEntry != nil) { + NSData *infoData = [NSJSONSerialization dataWithJSONObject:hashInfoEntry[@"info"] + options:0 + error:nil]; + if (infoData != nil) { + [defaults setObject:[[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding] + forKey:PushyHashInfoKey(hashInfoEntry[@"hash"])]; + } + } + if (hashToActivate != nil) { + PushySwitchVersionLocked(hashToActivate); + } + if (cacheData != nil) { + [defaults setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding] + forKey:keyNativeCheckCache]; + } + committed = YES; + }); + return committed; } // Sequential fallback over the ordered candidates (§5.1): one request at a

    a-$m(b-%#>(#=c3{A*rHGu;UL z;1`qb`f!^|fhBHqUAwy1{pzB#37xscgGG=3A#DB1gNhnYJZzb(_^en|^wn-oHd|(Z zi!Q~D&OFZOzNT|sggI`6oBxMyql>Q0jjk;|;&-vA`Tq9%ql?i0!~7TP{~!bsAr^mvEpIm7!w)7zCU*R~LQoVg{)*hB z{kX3)P0NbzbE&A@tZVLnk?A=6+*YvVnqT1}j6U^n<)-#?uZ#HS1Ir}4(cOX0G5=c* zn&(`E*=~eOo^%%2Qn$=SSL#ORW{{d4=0D^j)SiC0h0Vg2mZ@>k#Y5-w6wU+S%y9Tl zP`V1|jUO+!)U{7-whZR~LN^6=gUDT148JeLPjTJo_Nls-eZSa6SK+D?{VxjoUkk<_ z7op+I!MVRTPK+g&8D;o3a1Iv`S(f#~CbkknN{a^F;YjYzUI?;WB zAG;K2JpZs|I!<=4JM5xMap>^;-`xMgG;^l77uLB5bKDfzeyV%jV`&d8QwE(FGtJ{h zlsC&fjY@OiS`L~?E<*qBoqgz9QId>bD>#}shU798EeCBc=$Rlu!WiX|Ry zvt`K<`Z8U7!E)mWjSGk^rBVl!p@9pAi!8PDJxXKDwMoQL^2F!y)))R4#u3Ty3N~jr z@z;Eo-F#$SinDDQE+0<(S7hS9B{SZ{e_?`fCY-R{R!|&Si<9Mh?>}(P9G(9fw&5f1 zZFnA5J^xR{dieiZfdNi4IR4Yofxh^<74DE0pXm@uvwUAG0hM+;q&c>kJM2KUAhm&;S4c diff --git a/android/lib/x86/librnupdate.so b/android/lib/x86/librnupdate.so index c7a63b9b2311acbb52d390cf04e5e9c2a9fd30a3..3a74eb11e21971a82e1d4a96df141712abbb52f3 100755 GIT binary patch delta 33325 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�A*mCx=U{b%W=FqI+jsBwG`y9aZ^zSK4DYN*j&eK-D?X?_ z4+}yXJJ&a@jJzNtx5~(e>e24f`_Svf48gLYj<1`Euxw0fs|TVwBi---;2x zXVWnTfxm3F@0c7e`YfbY)+Jr6Sx8$3^sIbFy2p&i7i#D2h?7=)zJ(}RNc+3y&UJ>a zm@9hPX@a3I{IJki@qwMj8<^^w9Vf2YaVEFT5yKZj-K|H9&5K}R!FGYatBj~B#QA=R z9JuxlvJkJ zeH3^+TLkCO7nx5xF*S!y@u6?4*<(+jTIs#FWcFtx#l0M8>yx)c+!8u6z5A$Y`Qg!7 z<#w&Z`|8wQb(gloSjb*4#k^2sN>Bxa)i#9D94gx8R+ zAI9$Kpj$>~ytmK=3bySr=ON0)V}48NL)x9@3FOZ4D>kSxB?Atvq=S8l3geaRQCRJ}li02T1z@U?jhuK! zZRDWA(C1*sYtB5}WESY8G^^v$OnFOCS)V`mtMw_VYv)F}XMN;Y|2INP-H>9BTH{|r zok_HA%~bmZw^Nmmhve;I3Go?5?wfLQ1{0lWM(>PCUs|u(VUtR zHy1mDV0JLWcp0?`R)tjR<$$9nO3gZqy2fY0(DuHm8kbSJzu8w&Q6k?Vv&yMN3 z?4ZF`$LHTyR19|9O;v5DWrb?U98!1}CmB{Otwl8@c{%S3W0k#Wb$s`cmWLbi$U2nn z_!0qwCdu)aYQ;a1R_HWtupll9krWkNAKt*RyHi`3F9xcp>&g zhx{XJD0O9ThUfpdiIxShy&?H~f1q3Hu(Zzkul+)2xUhE3^RJzu>69f!<-c-{9@4S$ zPWjC)(-8d0N9X*02BxDS=kOAe?Dp;M_}Ml2q{~`4TiiA}pFic#sLft6Ht2|9Mwgb(?AAZcjOfjN`Kil8LB)4?P%Uch%_9(^C3>T2wogJ z0G8eMyULdmZBnQN2fV6~N{QY$o>vba*g>2MU<28zn0kquF~{>{ajhHQpU0ajS+puznecjVM{5R(lm*D*pg3t~gq`%z+k z5G!VL!$nFX_8klDC}K=3sll%}YdRYp@k>>sSDDy9?rl-JJzHnSzx-Hm{`z26%auJc ziEYi;W_G_#{)kBSHT9N%BmzA^S$6rC2C7DoIxLFWDVxwGKfeXLPuVBU#TPBvY<3DU zt(Z4smm>2mF>IQ`{95PlXvgX??`F}J8QsMM`81>R?U@^6_q*idrM>~R*hNczTzB@Z z!deB3@}8_$t#{CX;#j??m%w6O4u`|@g)kHRJ zAGV22FpCj=*-PxlaIvE=8_%w^5`q1gU+h7wDvmcgVqX*|JN7%4-KR9GUiQc`J0A|) zb_|wL!OutaZ!4xq>2$2*j$oOjL{1CUrDtFnceu!?!snyz1&e)mqmnrjZn?G{` z8%5dd=Hkpm#I))t-kSstYbx5k2JVf^zx5j1r}({ydlg*7#T$&ccIya_M?Iav)>G#z zXpYi0|NiT&31uByi-2kD1p6y8|9{ii`-*JTn={x#SC$@TFYKHTfqW zvlsLR^%S`^u*YJ9h-e3*A7ah4s0>#})MQuG&yb#ZCKe8Mn&~o|1&aJ<4jq zVdtM>1eXLp_1?l{R8kjSsY)Dc+6db~CGENLMcpJNVW7+n1;@n8G9V#KaaO0|CcLs7 zXOUU4*?r1N62GZiRiLltQH!hd4Hhr@W>l>sh)v|K}8CmceuvLsz!7)I}}t zA*OrT)>2p}ia#BmKYq6IC1ZU{ej%l|5+g1M(WYY#u=D!p`CRt$}iS&1f}DPx`s%U}MPlBQ=X z2jzQiR{AJx!l3*yTa^?lLr!f|0`%?=q;&SH(ssF9h{@$;Y z8M=_=a%I8aK#}ZOI(v%S{E8XB*lpi9CwjEl*0_0o(znVY%4&7UzrI`1QMSIZ_}?Do zB~~~{blt1G<$h6ZQeU;YDC+25WdvI|STxc+L zCP|HZyia+-eJ~2PXTOB`R1NFDU+JcWosg#vS?#s^m3ayq)kc`VSBlxi!TBBsm9CVT zhll|`D6N@phN9PA3Lm^sO|f{TuuK%&+poZ ze-s;bbg16kj$+#{ieKzhRMt9)PU>0Q6#9E6$v6x+8P%g0@3lW0btmM5lZs#6L+FO$ z)6%GHmQEt{q!N*S8BHw4mCLi~|CJV)`b_S?Xo7h(*^ z@PQ@0o(WHYt;_IUB`pz-C(hX;uU9s5gIQcYsd%#nk>bHgrFGr!v5c0~_W1gMJ=?2| zX#1-Y^nAyU`BjO0K5S(TVW(>dGoDgjX1bwb+9@T0nTCqprgY7BgswVJqrFT8L~B{%Lq$HqEPumqRv`(Lgq44+0rp^%a0Yp zg|8!x_ALU-@Rfz-r8rp#N}0@Lr*@LS1jxzHf*Rz>G)+FeS_ZOpTdD~b)F7CX>J&81 zs47^>-K-`j|8ttk%1X)#TeS4mD=g*G>yNIgsr>(EjCN#L{QDh0c0 za-;jQkmWrH89flMEq4Y-v%m%2!LdYVv4B&Vx5`vE{k~rC&vYe!$AfRV>EKT%ue?%+ zwRwS%S0>kDefxn^nE4zsdgXOwAj97@xHL_|uVM^hAtMHYqh0jVa1%5ZavTMX9Y=JB zk@48cF1nspaJtU$aThRE$9{wS*f&{7G~~ziVy7YivyhEoz*$fn-lMCNt{ z1x}gFTxEr(Orh+zK7>pit=E5r^i#(%uQcR8bv)6@bW>A_ZhSggGnv!f^uNLUUqXQ?D8RQ9IGMSPMR>{-onac}Qm_T;+ZCKnbZU62PPvZ?O`B2M z)B!DiqYn$oMFZYILwcjYH=r>$Nqzx6$PhGbWID(>n1gRZW6lR1-Y% zte`4abgpK(i`C>v?f+IZ_d=x{#=k6Bj$c_*w=q?tA+`p=!poI{&&qYLCYMr^+|`=p z-bk+|xUmL7)2*5XS5=cMszI&*uglh?(c{(ReDS~0r9ar%E&E|H{7KTTL*!&VO5& zj6X)I60DLdt0p(0205auSuSuaG?T6^61g=97UD;ZYbr2F3Tkp^YmiGZ)GW8Knw-(i z*}$j$FBgdv{0eeS1s<#>n4k$(_P>HleAbTz@e8U%A9YOd&*YI2Sm zGaklSh?mDqEO?U%0MGGe@h*4XHscqfO0n<<;bJ(rXZuzl>c|fsqrc zRC=Taxn%qpTupK#tI6Rf)1UUeY+$iF3$EC^DFv2Tbm+I$9PC+8Y9c89b+oLTe)PFyn3uoRha@ zPX`#7O~)hS&BGmK+%AC|*;Hc>HCDsiR~YCDU=B|gqmg-GYdGtrYGft_E{i9n`P}B9 zHQXCMhxJd#|3~m<0x-R(H7PO2fKqP&m;0(;LVw$HXZ&y7Euq1PqhB&di-t4#_m>l% z7auN*njHSjz;gy{$}EdV z#tXqH*T=X%aK)XtfPs_L(f~)$7W!kUaMTe7jz|Uv&<=)Lu+FC>ohO;|kzv>{0$p@5%-bF>ovjf>ZCw+$F|&;Oyaqav_fd zF1sJxfxuCJ{a3<6@IMS}L7mVVPsve0(qvmzs7A&XICW6wwiK}cV@|)r0_e^I9$*Hk zuxZp$`+eEqSB$&ub9DcQk&!4gxkEBI!@w-6cN)fU*~2o|_F6SEyTH+EFe`)XDd$K6 zB@*J3W-|k+t|{fv7$wjiMJ{!m+simAQ)(FFXz5t3qmIe_c?MG1(eeki22;mzIQ=kg z@NdTT2%P#jf&JPzx9W92G7f&Sr__(wjUr89d+bx}ST%2=lWXvz2}g3j!Iwa}a^RSU zHhTf1fl?Q7oQI0g2vmkjmlNs-?1KG)jjbb z$=~93Y8;O0wN2KG^^N!F<4smqn(B<+4L9h*pcR^E)xS4c4{o5%ZnLRPd)IlW2<0Fu z)k$SLD{C!(gS>@4gZ~|9X=~YqCh@)DOMjoVK5=?y5~?5C1MP!Gq0`VAXdJo@-GpYL zhCdT(8Po+6Rq7r=c^@ICLGl3C%)_;lNWLp_B)dK=rwyLCI~3mi)cx!rcG>mk=q4%Xr03i9jg+;bA$tp1y`}4ySSuUK zQ^@3$UPFBar`2EjN$OKM*omZmi~5uyFR4#bpQ1XM)Yqs_bxlK0n{WZ}Q!!|s3U Ci#&e- delta 32826 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 diff --git a/android/lib/x86_64/librnupdate.so b/android/lib/x86_64/librnupdate.so index d8fe2badaaf243cb6b01b16dc23efba68e65d7ed..d9d5b36e2e08eefe4fe8f375ee3e7b30f8da3d64 100755 GIT binary patch delta 53461 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 delta 53100 zcmb@v3tSdO`^UYrfZ~x`Ma4sUYp6sXP*Ks;z))`u71I)rVX2X+VPTRdFwr2S$Tf%T zgoZtoW|$h3Csa~2H8eA_QZ&jntguW;&HMe$?hbo%J@NVf-~apadFHuxzt`-{{N|dS zo!Ldwe{7Ol)+94X)#F|bo|hCJ>aB<$X_au}RADf6Ja~A5mCv%#JdT-u0y_=rBq2+dM9j=YkPO`S!9bVCO*ePv#y_T&;C7FetG)qwC;;qpbn>=?};gI}9wwWQC@GsVyJcjTO*PA?^aOWtq`b3i_ zDYAn2Ii@3-T=1O5GYK!W&a()Y6=V}0JI3tX+!{PdQIai3UJZw$lrAs_O#$Jnwwuai z1EheU=gj=YB)?|mFCpBh5A`c$#GzOf>x5@o4Xq@+#Nt(iS6W<_M;q#kdPM!zA;`8` z>aZFhFOWUsBs|{6F~fzkJQRzsr+mr7#^PqxnE z2+tgoWDbgW;*c#(BwRLB7DyYKO7icw8k$MCY-m;zaY)74grBks$|XG68gzMt%W?_` z|I;d`(Ber_L6!wd$OW?CQo=*Vnmt!WxV+7D!sTsQS(7Iz(ot2@p@dr%D3(ImP-l!; zu`l7VkC@y+c=&jeI|+|7&N2RESIG*bvJi5CR2D|K6cAnucM&dyMGG#*e|5zZ%$CNG z3mgxdJeKfmOPQ&}E=LNAC;6N2Fm4_t(Swf?NX9tX!y&kgc`D%ptb(!#uhKd+Z}sh{_K@tkc#?m$)pLo2%brVJ=xx+r9fDFTV3-&o^9nXCS3Mh3E{Hmq`>N)OR@%C z8M)w))pa`Ivg@h{cO{yGRz6B^Q@i-B3$-F2;uVf4l}vE zqLdeelM7_mNrh6G9CXnn{~uP*#SkugF0KYg{0l4)U&DcUcCR@m5(#(gGkG%M@fJ@d zyu{*}ggXjRju?Mg#1SnV_{k<*c3m#vvgh&$mpvy1(w-|seq;PgM~BahdX-{QknFlr z!gs%A=GO_oyw2oRgvY*Na^L5xXIS(8+nQxM0&Ir?Bo89|)&?_w7~vhP2Du2A`C|x= zdB?0@i6f5Lxn>24g#T$NOC|jAcg*}*gnNHz@?66EY%zI35^=o!w&^G){3)yJN(uMg zXy(@mpZUJYs|er8P42tM3_?l_ zPg4G|x-_n)Lpf;`lt}ntE5@mWFWhQYkVW{uO(xGJyucW9h<^cboU$q?CVZ8pxRmf; zWC#dXv(1XD2$v!AU5pTj@h7|b-S^B30))eZe{VG?i15d)9ttB|R_r1?+!}N-9$eI~ zgjsK6;yfIJS6CG%5a>o(3JeB^7QG)2iJJUY*uf0P2^7-_Y%nDAZJASxvs z1sOj&;jdaDt0KImHK=`;B-QNd71kgMSW=VEweF4}!h2gi6h?T{&1QpKgdewhC`NLl z|B?5Qr7TW5%C?s4iD2i+0CAIKU z!mag0l2S$-nf`e7g&&=8`IM_9Ts{@62$%U4ONhM7J329^6H^T#7{#9@5(ExfsG_Ps9>lz5b6Q4tYU5;j-(b zLfUi5B!7n0bE$;Op37QF*8jSdF`HZvPmAYArbVKukzzmhg)*A-Vg#1ORC z|0RUWL03w+?71?+WzR{0)jcQI|CQu|-B#CC5iYyVcUjE|D#ki@5H5QzpcbCwti=&T zxa_(R!jtbZdoGM{*%RS}%bs(Y{J+=#(c}Wzby8t<*I7HKSd#yW)pK!#%brWD!L>V` zT35dVNv+|sZ_LbEcoyNZkFp6*cA9;ZOSsiX8t>esmF$-Snn&B;x#dXN3B|-4WOYIb z;dk#c$8Ra&`g2;xF5RmIR}r@yEed%oO72Uz6zm{8`fgKj0O6M4A&QeYr1~Jj1GNpCA>PgWyB$a zs}nARTS<7lcD1X0FDuJy4wVMG&6&)XaHt>9%~l^o+){l=Ej)~HsXm-=XHQeTi*oaw zs&=WH9qt&KM{CmE4tE^!%5cXMe!nF+k#Goprn@aTi@2rW>{@s(;ZkrO;US@>-~z&{ z!(B)mGTg<4%W#(v9x4u72R&@{zAw}qB~rbk79K#jRPQ7_FWgig zM7SkJn-F4$JB;ShmWSBkb`h@(cQoN|S%PB-hv17Lw%|nKmV%RO;i-g6!I^|7KVS;Z zBD^}>*~B5kolCe3cOK#K+R2{w)LlrtB^v8xOx>+}*;;jyNouXEg;xc5s7;R|YqP@Y_~!!w5%kAL?xjjv;O-IJOoZN4OLmPk7!? zQ*a{T)xk|B4jJ53!ewwX36IwzLhXAwn|MR_m{WHy;ZXllsI9)3xTX4%T6ihpQhgcW zmBUT-I^i3!^Q`=-~QPEE5lt$_!&!Z72y!vE6f%gu(Iapk%FDI@F2pa z;1I&&Cz*o72(J!zIC02uy9k%zjwU=_>(Jj0cP#PN-)r98afCzt?Ebd;RN|KEGi%{l zgiH0=geOli)#nmkY4Tttk2s|I0>YhI!~i??#l$OPUqbi@OMNNfP``12t-gx5rFv!6 zzmExD!lil#;UQB^^#Ozznj9N!;*jct2$!)BAv|7t^*%fH;l#VbQtu)h>d)S1tB)gY zsXo3Io=CV$i%~YRCc&5pNl}zH0>az%UYTE|dvCk!58T&lKi!Jp9ghPGPLALr* z;+E>mYT-KJQhg=im6rM{${#g)Wr(7@SaWbm^}dA5*gFW1*Vte?_Dvl8T(AaAF$MC z5f1e)+;6KdAa1F?uohlSxKv+4c(kcfDJ8sm&MzYlsa_{s#=es9cx`^T9eX9U=15v< zsrMxu>OTv&)dvx`R3B0c4Ek zXu=(Z<{TG8I3oR6q^&-YxTX5!T6ikqQhg@j%4}187U9*A&L$42K9_JA={&;YwXs9( zyS$Kihgj;135WXiLv8gsaZB};weTv!rF!KhGKS`x>U{~f#Aw5Z*$epqnn#;I%no-D z@yc+A5I)Zm97Z?6X_j^GX%VG9l;ZYemt7VaWk3XUecO#Dk8{KODm9o$&rkim^3Tn0Cu z@OZ7~Nc&z+Cf2#xGM=CWC^Y!9D+Z7&=wr9y5{JSf}OSSAi|~K5W@4GHwA|gULEdm;*jBX z5iY|WO?bSvA<7PSEb%5;>f;E9`d^}K^{K=y)o0eivj~^!vk8x0Vye$2T!x#Evcp|K z^Jx1=+2JlGUK#EZ!na$3O9_YIj-zeCRm3d?D;fVj415Wff*q7EH3bI{UL9^Hama87 z5iY|WLO4EjkFlrjaN@nBMT{|~?nz^8t#Kri)EZw4Pb6GwO(r~KnW;6Ea2edX(ROgN zXdbO+v>n`B;+4V8BmAxd=HyjCID)$-+7?_&+){8^EnFvD3a%tPez_^Qity^-DlgX@ zJu}B;L`M`XIug{^(d+y^FY|`siAC4B=9JEa9;!rusO-Ww;kU zWQRME=Fv7hWQRMIcxAXV2~V~JXAus;>Ns0)0dY&gg|+Zv!lmF6!t+*|f=dan4tE)G z$Z+d~%Wzi`jt|}A?0Z>xrRLx%&^nAa?&SgFZLL8hlhhhg3lAe)Y7Hm6(9-H6Tn6{j zcssZ;G>_KgVLP~S#4CdvPxvoZa1#kfaG!bD7Mw-gQgC)HJeP1OIFIncbThaGgjWZ* zkT^0|n->%lE`wV_I6iAXV&BVU#M|VYmN!2-cbt=AY7_<5}uuD ziU}fIhI_&UJKSM3kG6b*9c~x#%5X;$KEe_lLpTIqoL~!1ByK49I26siAeC?#?o7h*p?k7Db!QWAxi%kO-TGb30^)UQ8z$TT&bF9%Wfzpx z!b=I4T~J22bFHaJr`-BQrG8VI2E1lwsiIl5=~G%J$zL(`T~l)~$&fn;A75lnX#s@G zZg3JV%LyX9tyxZz5<(oZA>p-f7vZueqif+YweVPjC)xiHKz4B)xgdF+*?;kb%R!S! zcxINFKbdfuKh=|qKeB=lXt$2>-{5c`4ztA9TXQ)|$buB;4wOB&F)# z9g1RIDF0=E>@r`%W!E?eFMQMNngGIOu$>0?m>}fZFsPObLI{^rt5hf(Dtj)R=4l z%(BCsO1v`MnS>v<1ZNQr!7t3}o>cgbIsOWW+mUVZLc(J$UQBqN#Y+gUw0J4uAsdYH zhbU#lA)g02;WFHngyS>%6ZZ2!SyyxLT+=o@VQlq3d%_NG5XmHi8&V4oBU}bIobY&4 zlj0&=2KR|LJGe14kM?Su9o#tLmBEcCyz?P*`bs1m!Tlr77Mw-gQgC)HJeP1OIFIn? zjn?2H+!8!QDI^ZL)F~!h2DgN8d?$a>zL(30cY>u}Cmia7XWQz1dCf5))jMk80fbBS zPQv9|tRTW=xWAcghdYes(b!XVxLw36!yQd{rX@Ira0q_vDO+$NaZAC;weVEJrQl4$ ztzUUlvIwsZcQ$dzdpVbI8SXs7@v(f4eJ>Xh?-6bJ9OGVIKgZUplT1=;Wi7mlaH&t}Qr*xTWCOT6i4c zQgA%s+3%Y7aw6f?!A&L(8QfIDWpFbI$EWdm_Pv}J3&Zi9@QdBHXE6c-oG=@9Q;3m5jZE@B~YJ0O3$S zB;JmF7;#JW;k9rV;WGBogcs(Ty&FS#b?jq_L#mG>T*f}0aC|wRZ^u5Fc;C0wrxFhJ zi{{(vbBSB3&#Q$O5H8gh5+1$R8e4>0Vze&L*tdEq&7+Nd#tyemyfWOCgrBhlR}l`u zA3tLY4p?7v^hm+ZT6hrQQg8_2$%UriFv6?D9ZnoF+%CdpxT6Wjm-A=saK{pF-C}d1 ziz6KBe|grv)l-REs?Q``-s)MDTeo^P;o195^|^#Q4j3H2&Q2ZHAA2hx+^OwPuw!3L zyfXGBgoj(|O9_YijtlJAR}r^Vue|Z^W5Snk8G8rev7ejj0|>8;YvGB6OZCZw=N&TDrxI?7(dsU=Z}lviN9(!J z4tFl`%5diq{;nmsfN%(2v#>|fVY5A@#Oo+Fc^Tm$7S{=vdMgQ!w(?gIZv6_k5}~|V zb2!Op`w}jr?I0ZA)f4R%o0E9I)wU%XE4HJFc5GcFlZ?3BM#Sv~b z9lx$l98zl{;rNR1ydCIN;+28UB;4nSIjdz6jzFu6Z1n}iE!7v+!ix!)>PrZ(JZg<3 z!Y%dKXA_51uM;jKT}e1TsV}ncYvrw)^ncWPIY?brtquZ(>N;e9OiVT42dL(38Sug%uQ z5VuqrTMLgPT&j;JJlZNhk?^uJM!iFnWa5zOQwf)`&mwEAL58l9omwNDq9=yzhH?sb|ui@4`9F09LsPy1| z9=ytfJ3P3OWA>L2*2IJRdhn)_i}C01a5R$+*Z>di@4=lOytxMt^588zc!&pY>A}Om zjqxv98sOmw_qd>y2X}e!)*d|CgSYYEF&@0F2amORl5&@aBhGdx?L2tA2XF7e6Fs=o zgC~3N4jw$!gLgEzb^m92IPUhiAj^XXdhl!y-pPaKdhpI3JkNu7@!$nX9*!UnN1+G5 z$AcGp@L&&K;=%9r;H4hCs|PQ$c#?>HHxGyIaY1(vUg^PmcT(}VkZ z@LuNM7dO2Q4@Ym03j#cN9}n*I;C(%KkO%MQ!9zTFST#p5!aN-Ps~z}H&3W(v+FLKT zY;~W9H`>EHP&*FqAP;Y>hj*~{54`t#c;h|1;aay>TDFSt@FshBhiFq@X}NvJ+RJSF z=5=emw!7B1_1Qk}osCVm`+Yo@b#DC#Q-&jTLK=cpv63mXRxza)(nCnikruAfHf}$Z zRJDaEr$1s!_zu&GW0}ggGo{T=`&{yWvo7RoW|dI@`_sRa_b=BaRadqE1w6KoDH{$l zW%hoiOe!*uSHZ5sx!^LbFEEb}SiI-wOqqPxWPz|5$os}_rZoMODKf9Twx&(83l)w| z#Z0Nfg$H3f?`2913T}Y&A{=XI$gx7E=wKr6+i2|KT}=4_%cbt%vS3kR-2+Tnb%-gO zVE3RAse736A{y2n$D@$nd6X$*jv*z9|3f-2fpsp}M@Oto7JD0&8W(xwX@={n3-jPK zpXYhn7iKvpVUuR!|8}8}J{Ei!cBpmi*?d#_8A;Nt zlmE?-C+WX3euN8qonlJ#*BHa!Fr}wQ3O-|=F9I9lX3Bkcn9>S12uWDa)R&w~-=G>i zi77n*Ha1k1q9&@++o3A2AdU4?mB6N|atG-=&JQ+Im7`#D>Z!`j#;THwhn|2z2&{%`hg`Thr%^sj|` zUgT3tMXkX9!p|Z6jQCzv{`^i=f?*vyREq5_x(fJ%F?r>0YF9NOvPW)g#GENymXGWCzl2BsbD|q?RFGN*AQMNCS|52NQX| zLAr$09_&ll8%T$d{BYg_NtE$rUoT}PQj30GN>8NUaU2Ev1X4Is5>kDnXT`yfaNa?h zjN@-$JHZyi9)nGUT?+dO(ql*)khUXbA$^XNfV3NFBGOYxA0R~|twI`(bUoZlx!m7N z$-56dGzjIw?jPu-e2U}iNJ(D|_EKVzo`a(iKxa4y;kZB8SfoAodnt{Pdb}dE7`1 zaNGmwZydLT{S)?X*!z$~8Jl4*BJD#eLK5dU@b@x3(=D)5N%+6dtPEp@VUQqgMw*T^ z4i~gR%E#ZGOdDsW9ivVADr_{;e~?Cu!eE1Kg(UKd6pQ1v;1giqM)DftrF2AUh*X60 z-(mZ~CS6CSA8^n(8VYe-1xE;u?}u#glQBaQ5 zAJ>dSx{XHsFH+L~Ce!~ZPtJpg_5YX<|NlAD|1FO{D*qo-(*GvY|1nP-!tgi7%>xL_ ze%Pf5T`a;SLVI~E?iAQ0aUfGXGCq&E^@U>)(o;w~k&Yw1jZ}`b0!id~6m}ufexzum zSCEDxeSq{Wk|^VQ7Y6VMFC`f%3~3tDY@~5W3y>m5cqJK`df{X+(hQ_~kVYZ}B27e! zM-qkn2QA-@^cm7Y6zqjT*Ba@2ocDwsge3BefV~0xFzj(y9Z3w>1F(LuqKtV+3sFY? zD8&B+PBM{xLb{4H8R;LS%}Did!E)F)k2_zT(K4>7)K%@|)WRj>q`-Iz_ zf5z?Jg;cKu5AK6*_g6_L-ERH3+kM|zw>$8F+buG^{I%Qt4fu24x!to4x!p%jx!ps* zb-VZB!p1lkg&YS9IP7+xMZWV$Phtho2!;B7?{+suo_RPg2frWXUWQ#?go=yZ?&72H zBcI#N8Y+01vOVN*uX}LGJYmgT(E0l=fLimh5WGIPcS7^0cHIBXa25-Suo6m|w|4D1frIk4lOL;z*1~3s46v(_p&Ygt=YjLVaUKd=j`IZA z9XL;c^~KJ4$1})>^V6{9;yTzWSnp?<5`edB{;*wPLt&#}BVl7OnC0N8TaYp|ymW4nRZbZeKO zVl*&k1$q$YDJiHJ=WAa<#W?rQ#O5js2d8mR3|qbl7vMEs`G2st8H|kBXvg5(e>+pM zU{Avq!=_;4d`<9A(8YMY7nskK@vxy{8wq<2b_eVZY$MBH6R-tsh}V6g*rtZTUW1(h zo3Iy~t3=`0haP|}hb@84!Cv(??2P?Tg7<-e*rSezEr(4O7W>>h*lVywu-@43mckan zHpI|A4eNyUKaA^ObBb{tY#_F)YlR=S5O)00Bn%cD%)r)AyhogY?P4hGY3x%|L=Rw> z7l-r6uW+HT*wr0_O({i;VaI=m2I4(q&IR-o?2L;DA+9TeEr$*K5e>rolN8u6*do|i zSnqQ56l`P?4)Sm?<2out0iie0b+F!5C;+w`)*mk{%m2nGhTU-!gB3RA7J3YJ#%;X& zfQ|fzDc*R28QMZs2Ey)Wr7Cf-32m`&f*s#pRZ3vJovLzM@D8fdwJly^MPhRmgM%V$ zu2NwWutO_=4a9D(9QHJ<<1Q%dhJsKM79SQsVecX0|r zw5kL-(IEWCo;}kr3W`O>dAI%g1Xa@c{e3Cocmb_Z-GY~%~*Vb~nl+es)eX9ZdckdmS*{&-b; z4R#=`_eyjjZ0IV)Dgp}ARAmXy178xGXzcRK*1wxm#5dU}wOl!WO}9fxWgzRZ7MAUR;NF z%WLu9g$;pCC`3iDk^9j=*uVpLhXPv!TPpkqlhCqz0S>B40IYuzDukT@8xETTJ07+i zHV!uMbBqBf@;-!6;5-F(3+(vA*tx@AgZ1tTe=*8~Jq_!EO*n@9utmr5Vn*<<&;VhR zzEKt5ZYZb}gA6wGTUCjLEr(qVyY?&w3+#>`FgRho&#Ow??l^~a!KRdyjSe;N59iT0)EA9 z1Do?31{G}N@3?JX%daA2unB+QI@rKJF*bTa5o|bY(KYlKZ24cv54-j{#u97}>}lA* zDuk*R?*DQebnS%-|Hg%|Yj5I0*zvazGuRoo5i{6GHwKlk3U(I=L7?iTbR7akUf7+& z?(oLexHl?>-2z(>X0jzH*8d4XFJJ|8C zVGrN}Uo2MzuZLAC&ac77;C!vaOG$-2-2{7V*lSI_lrq>N{8tv8eQ*vN1{>JiOBoNl z7B*Gb7RV1<)DroVa4w8ti(N;q)PFbu$nE`^=Z0lQJy z9N0M6a@Z8X@lI(AY!PfB>}lB3!Vjy%df$y6><=D@WihOGC-fX_PB2~;z?S#Hb_zBW zZzqZb5A%9dX*d8qa38t~HWHiuSaFU8dM2#cpB2IuVRv#xSggGR?n5lG!*IcRV+WG} zoA402T!aud680Kw4D60^+U!d&tEmrZH!rpJS@IB`q5RdBEI{k2H}iRO92SIH#Fb`u zT~QPz6K5S@#Sdm6;}i52uq18lpDnakbo|+d@l5d(9{h+L;Y?9(BS*TC<4e7XPb_jY z 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 { const decisionJson = NativePatchCore.handleCheckResponse( responseText, JSON.stringify(identity), + config.afterDownload ?? '', ); if (!decisionJson) { return; @@ -210,8 +212,9 @@ async function runOnce(context: UpdateContext): Promise { context.setKv(`hash_${hash}`, JSON.stringify(hashInfo)); } - if (config.afterDownload === 'setNeedUpdate') { - // 仅静默策略:激活到下次启动;alert 类策略把激活权留给 JS(§6)。 + if (decision.activate === true) { + // 静默策略、或服务端按版本标记的 forceBoot(远程覆盖,救砖指令):激活 + // 到下次启动;否则激活权留给 JS(§6)。 try { context.switchVersion(hash); logger.debug(TAG, `downloaded ${hash} and set for next launch`); diff --git a/harmony/pushy/src/main/ets/NativePatchCore.ts b/harmony/pushy/src/main/ets/NativePatchCore.ts index 95038b66..93a838ba 100644 --- a/harmony/pushy/src/main/ets/NativePatchCore.ts +++ b/harmony/pushy/src/main/ets/NativePatchCore.ts @@ -98,6 +98,7 @@ interface NativePatchCoreBindings { handleCheckResponse( responseText: string, identityJson: string, + afterDownload: string, ): string | undefined; } diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 401c8caf..567db901 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -1322,7 +1322,8 @@ + (void)runOnce { } flowjson::Value decision = updateflow::HandleCheckResponse( - PushyToStdString(responseText), identity, false); + PushyToStdString(responseText), identity, false, + config.Get("afterDownload").AsString()); if (decision.Get("action").AsString() != "download") { RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", decision.Get("reason").AsString().c_str()); @@ -1358,9 +1359,10 @@ + (void)runOnce { [defaults setObject:[NSString stringWithUTF8String:flowjson::Stringify(hashInfo).c_str()] forKey:PushyHashInfoKey(hash)]; - if (config.Get("afterDownload").AsString() == "setNeedUpdate") { - // Silent strategies only: activate for the next launch. Alert-style - // strategies leave activation to the JS side (§6/§10.1). + if (decision.Get("activate").Truthy()) { + // Silent strategies or a server-marked forceBoot version (per-version + // remote override — the brick rescue): activate for the next launch. + // Otherwise activation stays with the JS side (§6/§10.1). NSError *error = nil; if ([[self engine] switchVersion:hash error:&error]) { RCTLogInfo(@"RCTPushy -- native check: downloaded %@ and set for next launch", hash); diff --git a/scripts/generate-flow-vectors.ts b/scripts/generate-flow-vectors.ts index ea416bbe..7e1f7556 100644 --- a/scripts/generate-flow-vectors.ts +++ b/scripts/generate-flow-vectors.ts @@ -20,6 +20,7 @@ import { murmurhash3_32_gc, orderEndpointCandidates, resolveCheckResult, + shouldActivateAfterDownload, } from '../src/updateFlowCore'; const impls: Record any> = { @@ -30,6 +31,7 @@ const impls: Record any> = { buildCheckRequestBody, resolveCheckResult, decideDownload, + shouldActivateAfterDownload, }; export interface FlowVector { @@ -248,6 +250,29 @@ export const buildVectors = (): FlowVector[] => { add('decideDownload', dlInfo, dlIdentity, true); // dev: full only add('decideDownload', { ...dlInfo, full: undefined }, dlIdentity, true); // devNoop + // shouldActivateAfterDownload — silent strategies opt in locally, the + // server's per-version forceBoot overrides remotely (JS truthiness) + add('shouldActivateAfterDownload', { hash: 'x' }, 'setNeedUpdate'); + add('shouldActivateAfterDownload', { hash: 'x' }, 'none'); + add( + 'shouldActivateAfterDownload', + { hash: 'x', config: { forceBoot: true } }, + 'none' + ); + add( + 'shouldActivateAfterDownload', + { hash: 'x', config: { forceBoot: false } }, + 'none' + ); + add( + 'shouldActivateAfterDownload', + { hash: 'x', config: { forceBoot: 1 } }, + 'none' + ); + add('shouldActivateAfterDownload', { hash: 'x', config: {} }, 'none'); + add('shouldActivateAfterDownload', { hash: 'x', config: { forceBoot: true } }); + add('shouldActivateAfterDownload', { upToDate: true }, 'none'); + return cases; }; diff --git a/src/type.ts b/src/type.ts index ad7725fb..b6c57a4c 100644 --- a/src/type.ts +++ b/src/type.ts @@ -9,6 +9,14 @@ export interface VersionInfo { rollout: { [packageVersion: string]: number; }; + /** + * Server-set per-version override: the native cold-start check activates + * this version for the next launch regardless of the client's + * updateStrategy (the brick-rescue directive). Native-only — the JS + * interactive flow ignores it. The device-local rolledBack guard still + * wins, and first_time crash protection still applies. + */ + forceBoot?: boolean; [key: string]: any; }; pdiff?: string; diff --git a/src/updateFlowCore.ts b/src/updateFlowCore.ts index e4bdc528..de3adc6b 100644 --- a/src/updateFlowCore.ts +++ b/src/updateFlowCore.ts @@ -220,6 +220,24 @@ export function resolveCheckResult( return rootResult; } +/** + * Whether the native orchestrator should activate a downloaded version for + * the next launch: either the client opted in via its silent strategies + * (afterDownload === 'setNeedUpdate'), or the server marked this version + * `config.forceBoot` — the per-version remote override that closes the + * brick-rescue gap for alert-strategy apps (a bricked device never runs JS, + * so activation cannot wait for it). Native-only: the JS side's interactive + * strategies are not consulted and not affected. The device-local + * rolledBackVersion guard in decideDownload still wins over forceBoot, and + * the activated version keeps the first_time crash-protection rollback. + */ +export function shouldActivateAfterDownload( + info: CheckResult, + afterDownload?: string +): boolean { + return afterDownload === 'setNeedUpdate' || !!info?.config?.forceBoot; +} + export type DownloadStrategyType = 'diff' | 'pdiff' | 'full'; export interface DownloadAttempt { From 8412b64d63874f457060398eeeb38d9e71a57b28 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Sun, 9 Aug 2026 17:53:21 +0800 Subject: [PATCH 16/29] =?UTF-8?q?docs:=20record=20the=20config=20placement?= =?UTF-8?q?=20verdict=20=E2=80=94=20forceBoot=20lives=20on=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit versions.config belongs to the retired gray-release design and is being actively scrubbed by the binding transaction; the first draft that revived it through the binding branches was reverted (legacy rollout leakage, dual config sources, working against the scrub). forceBoot is a property of a delivery, so it lives on bindings.config — the per-delivery slot the upsert API already plumbs — which also self-clears on rebinding and scopes the directive to a single packageVersion. Client protocol unchanged: the response config stays synthesized from binding data. Co-Authored-By: Claude Fable 5 --- NATIVE_CHECKUPDATE_DESIGN.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 51925742..5f7b1a64 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -340,14 +340,15 @@ appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` → action=download:按 attempts 顺序走现有下载器(diff→pdiff→full, testUrls 语义由原生逐个尝试实现);成功 → setLocalHashInfo(info 的 name/description/metaInfo) → 按 afterDownload 决定是否 setNeedUpdate -→ 响应原文 + 时间戳落盘(§10.3) +→ 原生处理完成后,将响应原文 + 时间戳 + 请求/配置指纹落盘(§10.3) ``` ### 10.3 与 JS 的去重(§6 的落地形态) -首版**原生只写缓存**:响应原文 + 时间戳落到固定文件。紧随其后的 JS 小改 -动:`checkUpdate` 先读该缓存,时间戳新鲜(暂定 2 分钟)则直接复用不发请 -求。改造前的过渡期是双检查——多一次网络请求,服务端有缓存,无害。 +首版**原生只写缓存**:响应原文 + 时间戳 + 请求/配置指纹落到固定文件。紧 +随其后的 JS 小改动:`checkUpdate` 先读该缓存,时间戳新鲜(暂定 2 分钟) +则直接复用不发请求。改造前的过渡期是双检查——多一次网络请求,服务端有 +缓存,无害。 ### 10.4 失败策略 @@ -390,6 +391,27 @@ afterDownload)`:本地 silent 策略 或 响应标记 forceBoot 即激活。 - **first_time 崩溃保护对强制版本依然生效**:强制启动的版本若也是坏的, 下次启动照常回滚,不存在"强制进入坏版本且无法回头"。 -服务端配合(pushy-server / cresc-server,另行实施):根版本响应透传 -`config`(现只有 expVersion 携带);控制台版本编辑与重绑界面加 forceBoot -开关。 +**服务端存储位置的裁决(2026-08-09,推翻初稿):forceBoot 存 `bindings.config`, +不存 `versions.config`。** 两个 config 的意图沿革必须记清,防止再犯: + +- **`versions.config` 属旧灰度设计,已弃用且在被主动清洗**。旧设计把 + rollout 存在版本上(`config.rollout[packageVersion]`);新设计把 rollout + 搬到 `bindings.rollout` 列,客户端协议里的 `config.rollout` 形状由服务端 + **从绑定数据合成**。绑定事务里的 `removePackageRolloutConfig` 每次重绑都 + 会从 versions.config 清掉对应的 legacy rollout 键——决策层对 + versions.config 刻意不读,响应里的 config 一律合成,这是现行铁律。 +- **初稿曾把 forceBoot 放进 versions.config 并让绑定路径透传它,已否决**: + 透传会把未被清洗的 legacy rollout 连带泄漏回响应、让双 config 源复活、 + 与清洗机制逆行。相应改动在四个仓库均已回退。 +- **`bindings.config` 是新设计预留的"这次投放"配置位**(upsert API 全线 + 打通、快照本就 select 它),forceBoot 正是投放属性:救砖 = 把包重绑到正 + 常版本这一动作。存绑定还带来正确的生命周期——重绑即替换绑定,救援结束 + 后标记自动消失,不会像挂在版本上那样永久残留;粒度也收敛到单个 + packageVersion。 +- **客户端协议不变**:客户端仍读响应里 `info.config.forceBoot`,它从哪合成 + 客户端不感知。 + +已实施(四仓库,本地提交待推送):pushy-server / cresc-server 决策层从 +`binding.config.forceBoot` 合成进下发 config(灰度与全量两分支),绑定列表 +接口补 select config;pushy-admin / cresc-admin 发布菜单加"全量+强制启动 +(救砖)",已绑定行显示⚡标记 + 切换项(重发同绑定翻转标记)。 From f679e119a777ae685f0f07002170897931af8101 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Sun, 9 Aug 2026 18:51:30 +0800 Subject: [PATCH 17/29] fix: harden native cold-start update orchestration --- .../modules/update/DownloadTask.java | 19 +++ .../update/NativeCheckOrchestrator.java | 85 +++++++--- .../modules/update/UpdateContext.java | 18 ++- harmony/pushy/src/main/ets/DownloadTask.ts | 12 ++ .../src/main/ets/NativeCheckOrchestrator.ts | 71 +++++--- harmony/pushy/src/main/ets/UpdateContext.ts | 13 +- ios/RCTPushy/RCTPushy.mm | 153 +++++++++++++----- src/NativePushy.ts | 5 +- src/__tests__/client.test.ts | 148 ++++++++++++++++- src/client.ts | 89 ++++++++-- 10 files changed, 495 insertions(+), 118 deletions(-) diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index f5d3dbb7..ed6a5e88 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -462,6 +462,25 @@ && isPatchTask(taskType) return; } + if (isPatchTask(taskType)) { + try { + File marker = new File( + params.unzipDirectory, + UpdateContext.VERSION_COMPLETE_FILE + ); + if (!marker.createNewFile() && !marker.isFile()) { + throw new IOException("Failed to mark completed update: " + marker); + } + } catch (Throwable error) { + Log.e(UpdateContext.TAG, "failed to mark completed update", error); + cleanUpAfterFailure(taskType); + if (params.listener != null) { + params.listener.onDownloadFailed(error); + } + return; + } + } + // The task itself succeeded. Run the completion callback outside the // try/catch above so an exception thrown by the callback (e.g. a // FileProvider misconfiguration during installApk) is not mistaken for diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java index 41d2ad88..b9bda644 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java @@ -2,7 +2,6 @@ import android.os.Build; import android.util.Log; -import java.io.File; import java.util.HashSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -27,8 +26,8 @@ */ final class NativeCheckOrchestrator { static final String KEY_CONFIG = "nativeConfig"; - // Raw response cache for the JS side to reuse (§10.3): - // {"ts": , "body": }. + // Raw response cache for the JS side to reuse (§10.3), scoped to the + // exact logical request and native config that produced it. static final String KEY_RESP_CACHE = "nativeCheckResp"; private static final AtomicBoolean scheduled = new AtomicBoolean(false); @@ -36,7 +35,7 @@ final class NativeCheckOrchestrator { private NativeCheckOrchestrator() { } - static void schedule(final UpdateContext context) { + static void schedule(final UpdateContext context, final String launchRolledBackVersion) { if (UpdateContext.DEBUG) { return; } @@ -50,7 +49,7 @@ public void run() { // Keep the check away from the cold-start critical path // (§7 R5) — its result targets the NEXT launch anyway. Thread.sleep(5000); - runOnce(context); + runOnce(context, launchRolledBackVersion); } catch (Throwable e) { // The rescue path must never take the app down with it. Log.w(UpdateContext.TAG, "native check failed: " + e); @@ -62,7 +61,10 @@ public void run() { thread.start(); } - private static void runOnce(UpdateContext context) throws JSONException { + private static void runOnce( + UpdateContext context, + String launchRolledBackVersion + ) throws JSONException { String configJson = context.getKv(KEY_CONFIG); if (configJson == null || configJson.isEmpty()) { // No persisted config (old integration / first ever launch): the @@ -84,7 +86,11 @@ private static void runOnce(UpdateContext context) throws JSONException { } String currentVersion = context.getCurrentVersion(); - String rolledBackVersion = context.rolledBackVersion(); + // Snapshot captured on the launch path before getConstants consumes + // the one-shot rollback marker. Reading SharedPreferences here, five + // seconds later, would lose the guard and could forceBoot the version + // that this very launch just rolled back. + String rolledBackVersion = launchRolledBackVersion; String uuid = context.getKv("uuid"); if (uuid == null) { uuid = ""; @@ -92,9 +98,10 @@ private static void runOnce(UpdateContext context) throws JSONException { JSONObject identity = new JSONObject(); identity.put("packageVersion", context.getPackageVersion()); - if (currentVersion != null) { - identity.put("currentVersion", currentVersion); - } + identity.put( + "currentVersion", + currentVersion == null ? JSONObject.NULL : currentVersion + ); identity.put("uuid", uuid); if (rolledBackVersion != null) { identity.put("rolledBackVersion", rolledBackVersion); @@ -103,14 +110,17 @@ private static void runOnce(UpdateContext context) throws JSONException { JSONObject cInfo = new JSONObject(); cInfo.put("rnu", config.optString("rnu", "")); cInfo.put("rn", config.optString("rn", "")); - cInfo.put("os", "android " + Build.VERSION.RELEASE); + // React Native's Platform.Version is the Android SDK integer; use the + // same value so this request can be fingerprinted against the JS one. + cInfo.put("os", "android " + Build.VERSION.SDK_INT); cInfo.put("uuid", uuid); JSONObject input = new JSONObject(); input.put("packageVersion", context.getPackageVersion()); - if (currentVersion != null) { - input.put("currentVersion", currentVersion); - } + input.put( + "currentVersion", + currentVersion == null ? JSONObject.NULL : currentVersion + ); input.put("buildTime", context.getBuildTime()); input.put("cInfo", cInfo); input.put("supportedDiffVersion", NativeUpdateCore.supportedDiffVersion()); @@ -128,13 +138,6 @@ private static void runOnce(UpdateContext context) throws JSONException { return; } - // Persist the raw response for the JS side to reuse (§10.3), - // regardless of what the decision turns out to be. - JSONObject cacheEntry = new JSONObject(); - cacheEntry.put("ts", System.currentTimeMillis() / 1000); - cacheEntry.put("body", responseText); - context.setKv(KEY_RESP_CACHE, cacheEntry.toString()); - String decisionJson = NativeUpdateFlow.handleCheckResponse( responseText, identity.toString(), config.optString("afterDownload", "")); if (decisionJson == null) { @@ -142,6 +145,7 @@ private static void runOnce(UpdateContext context) throws JSONException { } JSONObject decision = new JSONObject(decisionJson); if (!"download".equals(decision.optString("action"))) { + persistResponseCache(context, configJson, body, responseText); Log.i(UpdateContext.TAG, "native check: nothing to do (" + decision.optString("reason") + ")"); return; @@ -151,13 +155,15 @@ private static void runOnce(UpdateContext context) throws JSONException { return; } - boolean downloaded = - new File(context.getRootDir(), hash + "/index.bundlejs").exists(); + boolean downloaded = context.hasCompletedVersion(hash); if (!downloaded) { downloaded = performAttempts( context, decision.optJSONArray("attempts"), hash, currentVersion); } if (!downloaded) { + // The native attempt has finished, so JS may safely reuse the + // response and retry through its own strategy chain. + persistResponseCache(context, configJson, body, responseText); return; } @@ -190,6 +196,23 @@ private static void runOnce(UpdateContext context) throws JSONException { Log.i(UpdateContext.TAG, "native check: downloaded " + hash + ", activation left to JS"); } + // Publish the response only after native file/state work is complete; + // otherwise JS can observe it and start a competing download. + persistResponseCache(context, configJson, body, responseText); + } + + private static void persistResponseCache( + UpdateContext context, + String configJson, + String requestBody, + String responseText + ) throws JSONException { + JSONObject cacheEntry = new JSONObject(); + cacheEntry.put("ts", System.currentTimeMillis() / 1000); + cacheEntry.put("body", responseText); + cacheEntry.put("request", requestBody); + cacheEntry.put("config", configJson); + context.setKv(KEY_RESP_CACHE, cacheEntry.toString()); } private static final OkHttpClient httpClient = new OkHttpClient.Builder() @@ -216,6 +239,18 @@ private static String httpRequest(String url, String postBody) { } } + private static boolean isValidCheckResponse(String responseText) { + if (responseText == null) { + return false; + } + try { + new JSONObject(responseText); + return true; + } catch (JSONException e) { + return false; + } + } + /** * Sequential fallback over the ordered candidates (§5.1): one request at * a time with its own timeout; after the configured round fails, @@ -241,7 +276,7 @@ private static String runCheckRequest(JSONObject config, String appKey, String b } tried.add(base); String response = httpRequest(base + "/checkUpdate/" + appKey, body); - if (response != null) { + if (isValidCheckResponse(response)) { return response; } } @@ -271,7 +306,7 @@ private static String runCheckRequest(JSONObject config, String appKey, String b } tried.add(base); String response = httpRequest(base + "/checkUpdate/" + appKey, body); - if (response != null) { + if (isValidCheckResponse(response)) { return response; } } diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index 548165eb..d63a4c0d 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -47,6 +47,7 @@ public class UpdateContext { private static final int STATE_OP_CLEAR_ROLLBACK_MARK = 5; private static final int STATE_OP_RESOLVE_LAUNCH = 6; private static final String KEY_FIRST_LOAD_MARKED = "firstLoadMarked"; + static final String VERSION_COMPLETE_FILE = ".pushy-complete"; // Singleton instance private static volatile UpdateContext sInstance; @@ -531,11 +532,6 @@ public String getBundleUrl() { } public String getBundleUrl(String defaultAssetsUrl) { - // Integration guarantees getBundleUrl runs at every startup, which - // makes it the anchor for the once-per-process native update check. - // Cheap: an AtomicBoolean gate and a daemon thread spawn. - NativeCheckOrchestrator.schedule(this); - isUsingBundleUrl = true; StateCoreResult currentState = getStateSnapshot(); StateCoreResult launchState = runStateCore( @@ -567,6 +563,7 @@ public String getBundleUrl(String defaultAssetsUrl) { String currentVersion = launchState.loadVersion; if (currentVersion == null) { + NativeCheckOrchestrator.schedule(this, launchState.rolledBackVersion); return defaultAssetsUrl; } @@ -582,12 +579,23 @@ public String getBundleUrl(String defaultAssetsUrl) { continue; } launchVersion = currentVersion; + NativeCheckOrchestrator.schedule(this, rolledBackVersion()); return bundleFile.toString(); } + NativeCheckOrchestrator.schedule(this, rolledBackVersion()); return defaultAssetsUrl; } + boolean hasCompletedVersion(String hash) { + if (!isSafePathComponent(hash)) { + return false; + } + File versionDir = new File(rootDir, hash); + return new File(versionDir, "index.bundlejs").isFile() + && new File(versionDir, VERSION_COMPLETE_FILE).isFile(); + } + private String rollBack() { StateCoreResult currentState = getStateSnapshot(); StateCoreResult nextState = runStateCore( diff --git a/harmony/pushy/src/main/ets/DownloadTask.ts b/harmony/pushy/src/main/ets/DownloadTask.ts index 6ea5360b..0740a088 100644 --- a/harmony/pushy/src/main/ets/DownloadTask.ts +++ b/harmony/pushy/src/main/ets/DownloadTask.ts @@ -12,6 +12,8 @@ import NativePatchCore, { CopyGroupResult, } from './NativePatchCore'; +const VERSION_COMPLETE_FILE_NAME = '.pushy-complete'; + export interface PatchManifestArrays { copyFroms: string[]; copyTos: string[]; @@ -818,6 +820,16 @@ export class DownloadTask { default: throw Error(`Unknown task type: ${params.type}`); } + if ( + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP || + params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK + ) { + await this.writeFileContent( + `${params.unzipDirectory}/${VERSION_COMPLETE_FILE_NAME}`, + new Uint8Array(0), + ); + } } catch (error: any) { console.error('Task execution failed:', error.message); if (params.type !== DownloadTaskParams.TASK_TYPE_CLEANUP) { diff --git a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts index 89069a0a..af756605 100644 --- a/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +++ b/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts @@ -13,7 +13,7 @@ import type { UpdateContext } from './UpdateContext'; // PushyFileJSBundleProvider.getBundleUrl 不会被调用。 const TAG = 'NativeCheck'; const KEY_CONFIG = 'nativeConfig'; -// 供 JS 侧复用的原始响应缓存(§10.3):{ ts: 秒级时间戳, body: 原始响应 }。 +// 供 JS 侧复用的原始响应缓存(§10.3),同时记录请求与配置指纹以限定命中范围。 const KEY_RESP_CACHE = 'nativeCheckResp'; const REQUEST_TIMEOUT_MS = 10000; const START_DELAY_MS = 5000; @@ -76,25 +76,33 @@ interface Decision { interface RespCacheEntry { ts: number; body: string; + request: string; + config: string; } let scheduled = false; -export function scheduleNativeCheck(context: UpdateContext): void { +export function scheduleNativeCheck( + context: UpdateContext, + launchRolledBackVersion: string, +): void { if (scheduled) { return; } scheduled = true; // 结果本来就是"下次启动生效",延迟几秒让开冷启动关键路径(§7 R5)。 setTimeout(() => { - runOnce(context).catch((e: Object) => { + runOnce(context, launchRolledBackVersion).catch((e: Object) => { // 救援路径自身绝不能把应用拖垮。 logger.error(TAG, `native check failed: ${e}`); }); }, START_DELAY_MS); } -async function runOnce(context: UpdateContext): Promise { +async function runOnce( + context: UpdateContext, + launchRolledBackVersion: string, +): Promise { const configJson = context.getKv(KEY_CONFIG); if (!configJson) { // 无落盘配置(老接入/首启):静默不跑——这就是灰度开关。 @@ -115,16 +123,16 @@ async function runOnce(context: UpdateContext): Promise { } const currentVersion = context.getCurrentVersion(); - const rolledBackVersion = context.rolledBackVersion(); + // getConstants consumes the persisted rollback marker during startup; use + // the launch-path snapshot captured before that happens. + const rolledBackVersion = launchRolledBackVersion; const uuid = context.getKv('uuid') ?? ''; const identity: FlowIdentity = { packageVersion: context.getPackageVersion(), + currentVersion, uuid, }; - if (currentVersion) { - identity.currentVersion = currentVersion; - } if (rolledBackVersion) { identity.rolledBackVersion = rolledBackVersion; } @@ -138,15 +146,12 @@ async function runOnce(context: UpdateContext): Promise { const input: FlowCheckInput = { packageVersion: identity.packageVersion, + currentVersion, buildTime: context.getBuildTime(), cInfo, supportedDiffVersion: NativePatchCore.getSupportedDiffVersion(), bundleHash: await context.getBundleHash(), }; - if (currentVersion) { - input.currentVersion = currentVersion; - } - const body = NativePatchCore.buildCheckRequestBody(JSON.stringify(input)); if (!body) { return; @@ -158,13 +163,6 @@ async function runOnce(context: UpdateContext): Promise { return; } - // 无论决策结果如何都落盘原始响应,供 JS 侧复用(§10.3)。 - const cacheEntry: RespCacheEntry = { - ts: Math.floor(Date.now() / 1000), - body: responseText, - }; - context.setKv(KEY_RESP_CACHE, JSON.stringify(cacheEntry)); - const decisionJson = NativePatchCore.handleCheckResponse( responseText, JSON.stringify(identity), @@ -175,6 +173,7 @@ async function runOnce(context: UpdateContext): Promise { } const decision = JSON.parse(decisionJson) as Decision; if (decision.action !== 'download') { + persistResponseCache(context, configJson, body, responseText); logger.debug(TAG, `nothing to do (${decision.reason ?? ''})`); return; } @@ -193,6 +192,7 @@ async function runOnce(context: UpdateContext): Promise { ); } if (!downloaded) { + persistResponseCache(context, configJson, body, responseText); return; } @@ -224,6 +224,35 @@ async function runOnce(context: UpdateContext): Promise { } else { logger.debug(TAG, `downloaded ${hash}, activation left to JS`); } + // 仅在原生文件/状态工作结束后公开缓存,避免 JS 观察到响应后并发下载。 + persistResponseCache(context, configJson, body, responseText); +} + +function persistResponseCache( + context: UpdateContext, + configJson: string, + requestBody: string, + responseText: string, +): void { + const cacheEntry: RespCacheEntry = { + ts: Math.floor(Date.now() / 1000), + body: responseText, + request: requestBody, + config: configJson, + }; + context.setKv(KEY_RESP_CACHE, JSON.stringify(cacheEntry)); +} + +function isValidCheckResponse(responseText: string | undefined): boolean { + if (responseText === undefined) { + return false; + } + try { + const parsed = JSON.parse(responseText) as Object | null; + return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed); + } catch (e) { + return false; + } } async function httpRequest( @@ -284,7 +313,7 @@ async function runCheckRequest( } tried.add(base); const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); - if (response !== undefined) { + if (isValidCheckResponse(response)) { return response; } } @@ -312,7 +341,7 @@ async function runCheckRequest( } tried.add(base); const response = await httpRequest(`${base}/checkUpdate/${appKey}`, body); - if (response !== undefined) { + if (isValidCheckResponse(response)) { return response; } } diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index eec9124a..0c6fad20 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -540,9 +540,9 @@ export class UpdateContext { // ——alert 类策略下版本已下载但未激活,若不判在这里会每次冷启动重下一遍。 public hasDownloadedVersion(hash: string): boolean { try { - return fileIo.accessSync( - this.getBundlePath(assertSafePathComponent(hash)), - ); + const safeHash = assertSafePathComponent(hash); + return fileIo.accessSync(this.getBundlePath(safeHash)) + && fileIo.accessSync(`${this.rootDir}/${safeHash}/.pushy-complete`); } catch (e) { return false; } @@ -576,11 +576,6 @@ export class UpdateContext { } public getBundleUrl() { - // 接入约定保证 getBundleUrl 每次启动必经,借它锚定每进程一次的原生检测 - // (NativeCheckOrchestrator);dev 走 Metro provider 不会到这里,天然完成 - // debug 门控。开销仅一个布尔门 + setTimeout。 - scheduleNativeCheck(this); - UpdateContext.isUsingBundleUrl = true; this.trace('getBundleUrl:enter'); const stateBeforeLaunch = this.getStateSnapshot(); @@ -628,12 +623,14 @@ export class UpdateContext { continue; } UpdateContext.launchVersion = version; + scheduleNativeCheck(this, this.rolledBackVersion()); return bundleFile; } catch (e) { console.error('Failed to access bundle file:', e); version = this.rollBack(); } } + scheduleNativeCheck(this, this.rolledBackVersion()); return ''; } diff --git a/ios/RCTPushy/RCTPushy.mm b/ios/RCTPushy/RCTPushy.mm index 567db901..f4178cff 100644 --- a/ios/RCTPushy/RCTPushy.mm +++ b/ios/RCTPushy/RCTPushy.mm @@ -45,7 +45,7 @@ // update check; parsed on read by the orchestrator. Absent = check disabled. static NSString *const keyNativeConfig = @"REACTNATIVECN_PUSHY_NATIVE_CONFIG_KEY"; // Raw response cache written by the native cold-start check for the JS side -// to reuse (§10.3): {"ts": , "body": }. +// to reuse (§10.3), scoped to the request and config that produced it. static NSString *const keyNativeCheckCache = @"REACTNATIVECN_PUSHY_NATIVE_CHECK_RESP_KEY"; static NSString *const PushyErrorDomain = @"cn.reactnative.pushy"; @@ -53,6 +53,7 @@ static NSString * const BUNDLE_FILE_NAME = @"index.bundlejs"; static NSString * const SOURCE_PATCH_NAME = @"__diff.json"; static NSString * const BUNDLE_PATCH_NAME = @"index.bundlejs.patch"; +static NSString * const VERSION_COMPLETE_FILE_NAME = @".pushy-complete"; // error def — messages are human-readable; the stable cross-platform codes // live in cpp/patch_core/error_codes.h and travel in PushyErrorCodeKey. @@ -86,6 +87,45 @@ typedef NS_ENUM(NSInteger, PushyType) { // has not loaded yet. Guarded by the state lock. static NSString *pushyLaunchVersion = nil; +// JS and the bridge-free cold-start engine use different RCTPushy instances, +// but they must still share one download per target hash. Without this +// process-wide registry two NSURLSessionDownloadTasks race over the same +// archive path and the later unzip can delete the first task's valid output. +static NSMutableDictionary *PushyInFlightDownloads(void) { + static NSMutableDictionary *downloads; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + downloads = [NSMutableDictionary dictionary]; + }); + return downloads; +} + +static BOOL PushyRegisterDownload(NSString *hash, void (^callback)(NSError *)) { + NSMutableDictionary *downloads = PushyInFlightDownloads(); + @synchronized (downloads) { + NSMutableArray *callbacks = downloads[hash]; + if (callbacks != nil) { + [callbacks addObject:[callback copy]]; + return NO; + } + downloads[hash] = [NSMutableArray arrayWithObject:[callback copy]]; + return YES; + } +} + +static void PushyFinishDownload(NSString *hash, NSError *error) { + NSMutableDictionary *downloads = PushyInFlightDownloads(); + NSArray *callbacks = nil; + @synchronized (downloads) { + callbacks = [downloads[hash] copy]; + [downloads removeObjectForKey:hash]; + } + for (id value in callbacks) { + void (^callback)(NSError *) = (void (^)(NSError *))value; + callback(error); + } +} + // Serializes every read-modify-write of the persisted update state. The state // machine itself is a pure function (state_core), but callers run on different // threads (main thread bundleURL, module method queue, _fileQueue), so the @@ -305,7 +345,11 @@ + (NSString *)buildTime; // bundle — this is what lets a bricked hot update be replaced on the next // launch. Decisions come from cpp/update_flow_core; this class is IO glue. @interface RCTPushyOrchestrator : NSObject -+ (void)scheduleFromColdStart; ++ (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion; ++ (void)runOnce:(NSString *)launchRolledBackVersion; ++ (void)persistResponseCache:(NSString *)responseText + request:(NSString *)requestBody + config:(NSString *)configJson; @end // Shared by the getBundleHash RCT method and the native cold-start check. @@ -351,12 +395,8 @@ @implementation RCTPushy { + (NSURL *)bundleURL { - // Integration guarantees bundleURL runs at every startup, which makes it - // the natural anchor for the once-per-process native check. Cheap: - // dispatch_once + a delayed dispatch, outside the state lock. - [RCTPushyOrchestrator scheduleFromColdStart]; - __block NSURL *resolvedURL = nil; + __block NSString *launchRolledBackVersion = nil; PushyWithStateLock(^{ NSUserDefaults *defaults = PushyDefaults(); @@ -412,7 +452,7 @@ + (NSURL *)bundleURL if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) { pushyLaunchVersion = loadVersion; resolvedURL = [NSURL fileURLWithPath:bundlePath]; - return; + break; } else { RCTLogError(@"RCTPushy -- bundle version %@ not found, rolling back", loadVersion); state = pushy::state::Rollback(state); @@ -421,8 +461,13 @@ + (NSURL *)bundleURL } } } + // Capture before constantsToExport consumes this one-shot marker. + // The delayed native check must never forceBoot the version that this + // launch just rolled back. + launchRolledBackVersion = PushyFromStdString(state.rolled_back_version); }); + [RCTPushyOrchestrator scheduleFromColdStart:launchRolledBackVersion]; return resolvedURL ?: [RCTPushy binaryBundleURL]; } @@ -805,6 +850,11 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( return; } + if (!PushyRegisterDownload(hash, callback)) { + RCTLogInfo(@"RCTPushy -- join in-flight download for %@", hash); + return; + } + NSString *zipFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%@",hash, [self zipExtension:type]]]; // On failure, remove the partial version directory like Android/Harmony @@ -813,12 +863,27 @@ - (void)performUpdate:(PushyType)type options:(NSDictionary *)options callback:( // this can never resolve to the download root itself. NSString *unzipDir = [dir stringByAppendingPathComponent:hash]; void (^completion)(NSError *) = ^(NSError *error) { - if (error != nil) { - dispatch_async(self->_fileQueue, ^{ + // Settle every JS/native waiter only after cleanup or the atomic + // completion marker write has run on the process-wide file queue. + dispatch_async(self->_fileQueue, ^{ + NSError *finalError = error; + if (finalError == nil) { + NSString *marker = [unzipDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; + NSError *markerError = nil; + BOOL marked = [[NSData data] writeToFile:marker + options:NSDataWritingAtomic + error:&markerError]; + if (!marked) { + finalError = markerError ?: PushyErrorWithCode( + pushy::error_codes::kFileOperationFailed, + @"failed to mark completed update"); + } + } + if (finalError != nil) { [[NSFileManager defaultManager] removeItemAtPath:unzipDir error:nil]; - }); - } - callback(error); + } + PushyFinishDownload(hash, finalError); + }); }; RCTLogInfo(@"RCTPushy -- download file %@", updateUrl); @@ -1206,9 +1271,18 @@ + (NSString *)buildTime return result; } +static BOOL PushyIsValidCheckResponse(NSString *responseText) { + if (responseText == nil) { + return NO; + } + bool ok = false; + flowjson::Value parsed = flowjson::Parse(PushyToStdString(responseText), &ok); + return ok && parsed.IsObject(); +} + @implementation RCTPushyOrchestrator -+ (void)scheduleFromColdStart { ++ (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion { #if !DEBUG // Once per process; a few seconds of delay keeps the check away from the // cold-start critical path (§7 R5) — its result targets the NEXT launch. @@ -1217,7 +1291,7 @@ + (void)scheduleFromColdStart { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ @try { - [self runOnce]; + [self runOnce:launchRolledBackVersion]; } @catch (NSException *exception) { // The rescue path must never take the app down with it. RCTLogWarn(@"RCTPushy -- native check crashed: %@", exception.reason); @@ -1239,7 +1313,7 @@ + (RCTPushy *)engine { return engine; } -+ (void)runOnce { ++ (void)runOnce:(NSString *)launchRolledBackVersion { NSUserDefaults *defaults = PushyDefaults(); NSString *configJson = [defaults stringForKey:keyNativeConfig]; if (configJson.length == 0) { @@ -1258,12 +1332,11 @@ + (void)runOnce { } __block NSString *currentVersion = nil; - __block NSString *rolledBackVersion = nil; PushyWithStateLock(^{ pushy::state::State state = PushyStateFromDefaults(PushyDefaults()); currentVersion = PushyFromStdString(state.current_version); - rolledBackVersion = PushyFromStdString(state.rolled_back_version); }); + NSString *rolledBackVersion = launchRolledBackVersion; NSString *uuid = [defaults stringForKey:keyUuid] ?: @""; flowjson::Value identity = flowjson::Value::Object(); @@ -1309,22 +1382,11 @@ + (void)runOnce { return; } - // Persist the raw response for the JS side to reuse (§10.3), regardless - // of what the decision turns out to be. - NSDictionary *cacheEntry = @{ - @"ts": @((long long)[[NSDate date] timeIntervalSince1970]), - @"body": responseText, - }; - NSData *cacheData = [NSJSONSerialization dataWithJSONObject:cacheEntry options:0 error:nil]; - if (cacheData != nil) { - [defaults setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding] - forKey:keyNativeCheckCache]; - } - flowjson::Value decision = updateflow::HandleCheckResponse( PushyToStdString(responseText), identity, false, config.Get("afterDownload").AsString()); if (decision.Get("action").AsString() != "download") { + [self persistResponseCache:responseText request:body config:configJson]; RCTLogInfo(@"RCTPushy -- native check: nothing to do (%s)", decision.Get("reason").AsString().c_str()); return; @@ -1334,16 +1396,18 @@ + (void)runOnce { return; } - NSString *bundlePath = [[[RCTPushy downloadDir] - stringByAppendingPathComponent:hash] - stringByAppendingPathComponent:BUNDLE_FILE_NAME]; - BOOL downloaded = [[NSFileManager defaultManager] fileExistsAtPath:bundlePath]; + NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash]; + NSString *bundlePath = [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]; + NSString *completePath = [versionDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]; + BOOL downloaded = [[NSFileManager defaultManager] fileExistsAtPath:bundlePath] + && [[NSFileManager defaultManager] fileExistsAtPath:completePath]; if (!downloaded) { downloaded = [self performAttempts:decision.Get("attempts") hash:hash originHash:currentVersion]; } if (!downloaded) { + [self persistResponseCache:responseText request:body config:configJson]; return; } @@ -1373,6 +1437,23 @@ + (void)runOnce { } else { RCTLogInfo(@"RCTPushy -- native check: downloaded %@, activation left to JS", hash); } + [self persistResponseCache:responseText request:body config:configJson]; +} + ++ (void)persistResponseCache:(NSString *)responseText + request:(NSString *)requestBody + config:(NSString *)configJson { + NSDictionary *cacheEntry = @{ + @"ts": @((long long)[[NSDate date] timeIntervalSince1970]), + @"body": responseText, + @"request": requestBody, + @"config": configJson, + }; + NSData *cacheData = [NSJSONSerialization dataWithJSONObject:cacheEntry options:0 error:nil]; + if (cacheData != nil) { + [PushyDefaults() setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding] + forKey:keyNativeCheckCache]; + } } // Sequential fallback over the ordered candidates (§5.1): one request at a @@ -1395,7 +1476,7 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config NSString *response = PushyHttpRequest( [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], @"POST", body, 10); - if (response != nil) { + if (PushyIsValidCheckResponse(response)) { return response; } } @@ -1422,7 +1503,7 @@ + (NSString *)runCheckRequest:(const flowjson::Value &)config NSString *response = PushyHttpRequest( [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey], @"POST", body, 10); - if (response != nil) { + if (PushyIsValidCheckResponse(response)) { return response; } } diff --git a/src/NativePushy.ts b/src/NativePushy.ts index 589f7497..7949461d 100644 --- a/src/NativePushy.ts +++ b/src/NativePushy.ts @@ -25,9 +25,8 @@ export interface Spec extends TurboModule { */ syncNativeConfig(config: string): Promise; /** - * Raw response cached by the native cold-start check - * ('{"ts":,"body":}'), so the JS - * side can reuse a fresh result instead of re-checking (§10.3). Resolves to + * Raw response cached by the native cold-start check, including the request + * and config fingerprints that scope reuse (§10.3). Resolves to * an empty string when absent; never rejects. */ getNativeCheckCache(): Promise; diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 93a46a43..6e8671f3 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -1418,17 +1418,37 @@ describe('syncNativeConfig', () => { }); describe('native check cache reuse', () => { + const expectedRequestBody = JSON.stringify({ + packageVersion: '1.0.0', + hash: 'hash', + buildTime: '2023-01-01', + cInfo: { + rnu: '10.0.0', + rn: '0.73.0', + os: 'ios', + uuid: 'uuid', + }, + diffV: 2, + }); + test('a fresh cached response is reused without a network check', async () => { const cachedResult = { upToDate: true }; + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); const getNativeCheckCache = mock(() => Promise.resolve( JSON.stringify({ ts: Math.floor(Date.now() / 1000) - 30, body: JSON.stringify(cachedResult), + request: expectedRequestBody, + config: configJson, }) ) ); - setupClientMocks({ getNativeCheckCache }); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); // setup.ts's default fetch throws, so any network attempt would surface // as a failed check (undefined) instead of the cached result. const { Pushy } = await importFreshClient('native-cache-fresh'); @@ -1439,15 +1459,22 @@ describe('native check cache reuse', () => { }); test('a stale cached response falls through to the network', async () => { + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); const getNativeCheckCache = mock(() => Promise.resolve( JSON.stringify({ ts: Math.floor(Date.now() / 1000) - 600, body: JSON.stringify({ upToDate: true }), + request: expectedRequestBody, + config: configJson, }) ) ); - setupClientMocks({ getNativeCheckCache }); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); const networkResult = { update: true, hash: 'net-hash' }; (globalThis as any).fetch = mock(async () => createJsonResponse(networkResult) @@ -1460,7 +1487,8 @@ describe('native check cache reuse', () => { test('an unreadable cache never breaks the check', async () => { const getNativeCheckCache = mock(() => Promise.resolve('not json')); - setupClientMocks({ getNativeCheckCache }); + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); const networkResult = { upToDate: true }; (globalThis as any).fetch = mock(async () => createJsonResponse(networkResult) @@ -1470,4 +1498,118 @@ describe('native check cache reuse', () => { expect(await client.checkUpdate()).toEqual(networkResult); }); + + test('a cache for a different request body is never reused', async () => { + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 10, + body: JSON.stringify({ hash: 'wrong-hash', update: true }), + request: expectedRequestBody, + config: configJson, + }) + ) + ); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); + const networkResult = { update: true, hash: 'requested-hash' }; + (globalThis as any).fetch = mock(async () => + createJsonResponse(networkResult) + ); + const { Pushy } = await importFreshClient('native-cache-request-key'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate({ toHash: 'requested-hash' })).toEqual( + networkResult + ); + }); + + test('request key order does not prevent reuse of the same request', async () => { + const cachedResult = { upToDate: true }; + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 10, + body: JSON.stringify(cachedResult), + request: JSON.stringify({ + diffV: 2, + cInfo: { + uuid: 'uuid', + os: 'ios', + rn: '0.73.0', + rnu: '10.0.0', + }, + buildTime: '2023-01-01', + hash: 'hash', + packageVersion: '1.0.0', + }), + config: configJson, + }) + ) + ); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); + const { Pushy } = await importFreshClient('native-cache-key-order'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(cachedResult); + }); + + test('a cache for a different native config is never reused', async () => { + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) - 10, + body: JSON.stringify({ upToDate: true }), + request: expectedRequestBody, + config: JSON.stringify({ appKey: 'another-app' }), + }) + ) + ); + const syncNativeConfig = mock(() => Promise.resolve()); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); + const networkResult = { update: true, hash: 'net-hash' }; + (globalThis as any).fetch = mock(async () => + createJsonResponse(networkResult) + ); + const { Pushy } = await importFreshClient('native-cache-config-key'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(networkResult); + }); + + test('a cache timestamp from the future is never reused', async () => { + let configJson = ''; + const syncNativeConfig = mock((value: string) => { + configJson = value; + return Promise.resolve(); + }); + const getNativeCheckCache = mock(() => + Promise.resolve( + JSON.stringify({ + ts: Math.floor(Date.now() / 1000) + 600, + body: JSON.stringify({ upToDate: true }), + request: expectedRequestBody, + config: configJson, + }) + ) + ); + setupClientMocks({ getNativeCheckCache, syncNativeConfig }); + const networkResult = { update: true, hash: 'net-hash' }; + (globalThis as any).fetch = mock(async () => + createJsonResponse(networkResult) + ); + const { Pushy } = await importFreshClient('native-cache-future'); + const client = new Pushy({ appKey: 'demo-app' }); + + expect(await client.checkUpdate()).toEqual(networkResult); + }); }); diff --git a/src/client.ts b/src/client.ts index 925dafe0..3632b01e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -267,27 +267,21 @@ export class Pushy { this.syncNativeConfig(); }; - /** - * Persist the subset of options the native cold-start check needs - * (NATIVE_CHECKUPDATE_DESIGN §10.1). JS is the single config source; a - * native side without persisted config silently skips its check, so this - * is also the feature's rollout gate. Fire-and-forget: config sync must - * never affect the JS update flow. - */ - private syncNativeConfig = () => { + /** Serialize the subset of options used by the native cold-start check. */ + private getNativeConfigJson = (): string | undefined => { if ( Platform.OS === 'web' || typeof PushyModule.syncNativeConfig !== 'function' ) { // Older natives lack the method; on web PushyModule is a noop Proxy // and the feature-detect would false-positive. - return; + return undefined; } const { appKey, server, updateStrategy } = this.options; if (!appKey || !server?.main?.length) { - return; + return undefined; } - const config = { + return JSON.stringify({ appKey, endpoints: server.main, queryUrls: server.queryUrls ?? [], @@ -300,14 +294,63 @@ export class Pushy { : 'none', rnu: cInfo.rnu, rn: cInfo.rn, - }; - Promise.resolve(PushyModule.syncNativeConfig(JSON.stringify(config))).catch( + }); + }; + + private syncNativeConfig = () => { + const configJson = this.getNativeConfigJson(); + if (!configJson) { + return; + } + Promise.resolve(PushyModule.syncNativeConfig(configJson)).catch( (e: any) => { log('syncNativeConfig failed:', e?.message || e); } ); }; + private jsonStringsEqual = (left: string, right: string): boolean => { + const compare = (a: unknown, b: unknown): boolean => { + if (a === b) { + return true; + } + if (Array.isArray(a) || Array.isArray(b)) { + return ( + Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.every((value, index) => compare(value, b[index])) + ); + } + if ( + a === null || + b === null || + typeof a !== 'object' || + typeof b !== 'object' + ) { + return false; + } + const leftObject = a as Record; + const rightObject = b as Record; + const leftKeys = Object.keys(leftObject).sort(); + const rightKeys = Object.keys(rightObject).sort(); + return ( + leftKeys.length === rightKeys.length && + leftKeys.every( + (key, index) => + key === rightKeys[index] && + compare(leftObject[key], rightObject[key]) + ) + ); + }; + + try { + return compare(JSON.parse(left), JSON.parse(right)); + } catch { + return false; + } + }; + private providerMounted = false; /** * Called by UpdateProvider on mount. A second concurrently mounted @@ -630,7 +673,9 @@ export class Pushy { * undefined whenever the cache is absent, stale, or unreadable — any * failure falls through to a normal network check. */ - private readNativeCheckCache = async (): Promise => { + private readNativeCheckCache = async ( + requestBody: string + ): Promise => { try { if (__DEV__ || typeof PushyModule.getNativeCheckCache !== 'function') { return undefined; @@ -640,10 +685,20 @@ export class Pushy { return undefined; } const entry = JSON.parse(raw); - if (typeof entry?.ts !== 'number' || typeof entry?.body !== 'string') { + const configJson = this.getNativeConfigJson(); + if ( + typeof entry?.ts !== 'number' || + typeof entry?.body !== 'string' || + typeof entry?.request !== 'string' || + !this.jsonStringsEqual(entry.request, requestBody) || + !configJson || + typeof entry?.config !== 'string' || + !this.jsonStringsEqual(entry.config, configJson) + ) { return undefined; } - if (Date.now() / 1000 - entry.ts > 120) { + const ageSeconds = Date.now() / 1000 - entry.ts; + if (ageSeconds < 0 || ageSeconds > 120) { return undefined; } const result = JSON.parse(entry.body); @@ -812,7 +867,7 @@ export class Pushy { // the promise so no await lands between the dedup window above and the // lastRespJson assignment below (the JS2-1 double-send lesson). const respJsonPromise = (async (): Promise => { - const cached = await this.readNativeCheckCache(); + const cached = await this.readNativeCheckCache(stringifyBody); return cached ?? (await this.fetchCheckResult(fetchPayload)); })(); this.lastRespJson = respJsonPromise; From 181952ad5294de8f8b87b138e8605479128ac3ac Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Sun, 9 Aug 2026 23:17:19 +0800 Subject: [PATCH 18/29] fix: address native check review feedback --- NATIVE_CHECKUPDATE_DESIGN.md | 15 ++-- android/lib/arm64-v8a/librnupdate.so | Bin 290208 -> 292400 bytes android/lib/armeabi-v7a/librnupdate.so | Bin 183236 -> 184996 bytes android/lib/x86/librnupdate.so | Bin 267432 -> 269656 bytes android/lib/x86_64/librnupdate.so | Bin 291304 -> 293512 bytes .../update/NativeCheckOrchestrator.java | 45 ++++++++-- .../modules/update/UpdateModuleImpl.java | 2 +- cpp/update_flow_core/flow_json.cpp | 54 ++++++++++-- cpp/update_flow_core/tests/flow_vectors.json | 28 +++--- .../tests/update_flow_core_test.cpp | 9 ++ cpp/update_flow_core/update_flow_core.cpp | 33 ++++--- cpp/update_flow_core/update_flow_jni.cpp | 62 +++++++++++++- harmony/pushy/src/main/ets/DownloadTask.ts | 2 +- .../src/main/ets/NativeCheckOrchestrator.ts | 41 +++++++-- harmony/pushy/src/main/ets/PathUtils.ts | 21 +++++ .../pushy/src/main/ets/PushyTurboModule.ts | 8 +- harmony/pushy/src/main/ets/UpdateContext.ts | 33 +++---- ios/RCTPushy/RCTPushy.mm | 57 +++++++++--- scripts/build-harmony-har.js | 6 ++ scripts/generate-flow-vectors.ts | 14 +-- scripts/test-update-flow-core.sh | 4 + src/__tests__/client.test.ts | 81 ++++++++++++++++++ src/__tests__/provider.render.test.tsx | 1 + src/__tests__/resolveCheckResult.test.ts | 34 ++++++++ src/__tests__/updateFlowCore.test.ts | 21 +++-- src/client.ts | 56 ++++++++++-- src/provider.tsx | 6 +- src/updateFlowCore.ts | 35 ++++++-- 28 files changed, 546 insertions(+), 122 deletions(-) create mode 100644 harmony/pushy/src/main/ets/PathUtils.ts diff --git a/NATIVE_CHECKUPDATE_DESIGN.md b/NATIVE_CHECKUPDATE_DESIGN.md index 5f7b1a64..71ff0b92 100644 --- a/NATIVE_CHECKUPDATE_DESIGN.md +++ b/NATIVE_CHECKUPDATE_DESIGN.md @@ -108,7 +108,7 @@ guardian 救不了它。 > "单源"要保的性质是**语义唯一且被机械强制**,由金标向量提供: > `src/updateFlowCore.ts` 是参照实现(oracle), > `scripts/generate-flow-vectors.ts` 产出 -> `cpp/update_flow_core/tests/flow_vectors.json`(69 向量), +> `cpp/update_flow_core/tests/flow_vectors.json`(金标向量集), > `src/__tests__/flowVectors.test.ts` 钉住 TS 与向量文件一致, > `scripts/test-update-flow-core.sh`(CI cpp-test job,带 ASan/UBSan)钉住 > C++ 移植与向量一致。**纪律:语义改动先落 TS → 重新生成向量 → 移植 C++, @@ -281,7 +281,7 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示 为纯;client.ts / provider.tsx / endpoint.ts 已原地改用,单测覆盖 3. ~~**运行时选型**~~ **已关闭:改判方案 B**(2026-08-07,裁决与推理链见 §4)。`cpp/update_flow_core` 已实现(flow_json + 七个决策函数的 1:1 移 - 植),69 条金标向量在本机与 CI(ASan/UBSan)全过 + 植),金标向量集在本机与 CI(ASan/UBSan)全过 4. **原生编排** —— 三端 HTTP + 调用 `update_flow_core` + 复用现有下载/ patch/state;endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race。 Android 侧 `update_flow_core` 进 `librnupdate.so`(协议演进从此绑定 @@ -312,8 +312,9 @@ appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` 本。** 每次 `setOptions`(含构造)后 JS 调新的原生方法 `syncNativeConfig`, 持久化: -``` -{ appKey, endpoints: server.main, queryUrls, afterDownload: 'none' | 'setNeedUpdate', +```text +{ appKey, packageVersion, endpoints: server.main, queryUrls, + afterDownload: 'none' | 'setNeedUpdate', disabled?: boolean, rnu, rn } ``` @@ -329,13 +330,13 @@ appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` 冷启动 + 延迟 5s(R5),后台线程,每次冷启动至多一轮: -``` +```text 读 config(无则退出)+ 原生 state(currentVersion / rolledBackVersion / packageVersion / buildTime / uuid / supportedDiffVersion / bundleHash 缓存) → BuildCheckRequestBody(bundleHash 同步读缓存,缺省省略字段) → OrderEndpointCandidates(endpoints, 原生随机数) -→ 顺序请求,单个超时 10s;全失败 → 拉 queryUrls(任一成功即用)合并新候选, - 排除已失败的,再顺序一轮;仍失败 → 本轮放弃 +→ 顺序请求,单个超时 10s、整轮 HTTP 最多 8 次;全失败 → 拉 queryUrls + (任一成功即用)合并新候选,排除已失败的,再顺序一轮;仍失败 → 本轮放弃 → HandleCheckResponse(响应原文, identity, isDev=false)(已实现,含 info 透出) → action=download:按 attempts 顺序走现有下载器(diff→pdiff→full, testUrls 语义由原生逐个尝试实现);成功 → setLocalHashInfo(info 的 diff --git a/android/lib/arm64-v8a/librnupdate.so b/android/lib/arm64-v8a/librnupdate.so index c1930a6fb14eb021ec4a7805d1f9ced7331cd764..a05184e549c505c67d3b79a98f45b63f046b114b 100755 GIT binary patch delta 73836 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
    -I9$#F_}C_FN1DmBHn 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 delta 40234 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