Repository navigation
Commit 7e4e095
perf/fix: dedupe cold-start checks, move IO off UI threads, CI cost cuts (#654)
* ci: pin GitHub Actions to commit SHAs again
a9793d6 moved every action back to floating major tags while bumping
majors, which left publish.yml (id-token: write) trusting whatever a tag
points at on release day. Pin each one to the commit its current major
tag resolves to, with the exact release in a trailing comment so
Dependabot keeps the SHA/version pairs current (as dependabot.yml says).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): tighten native config sync, check cache reuse and retry paths
- readNativeCheckCache: judge entry age before parsing anything else,
compare configs with the shared canonical equality (C1:
nativeConfigsEquivalent in updateFlowCore), and stop reading the cache
for the rest of the process once it was seen absent or expired.
- markJsCheckCompleted: send once per config after it succeeds.
- syncNativeConfig: forget the synced value when ownership moves to
'native' so switching back re-writes; a NATIVE_CONFIG_CONFLICT
rejection (host called native configure()) now console.errors an
actionable message, goes through emitError/onError and stops retries.
- markSuccess: share the in-flight native call between concurrent
callers (one native call, one report); cleared once settled.
- performDownload: HEAD-rank mirrors once per strategy per download;
a failed winner is demoted instead of re-racing every attempt.
- provider: the Linking effect reads parseTestPayload through a ref and
handles the launch URL once, instead of replaying it on every
strategy-callback identity change.
- utils: web ping no longer passes an unbound Promise.resolve; inline
createNonSecureId replaces nanoid/non-secure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(harmony): move bundle/resource IO off the UI thread; config equivalence and conflict
IO (UI thread):
- SaveFile: rawfile -> sandbox copy now uses async fileIo (was openSync/
readSync/writeSync on the TurboModule thread); withRawFd serializes
same-name getRawFd/closeRawFd so one close cannot pull an fd from under
another reader.
- pdiff: stream the embedded bundle from getRawFd into the temp origin file
instead of getRawFileContent + write (which also wrote the whole
underlying ArrayBuffer of the view).
- bundleHash: new sha256HexRawFdAsync(fd, offset, length) in pushy.cpp
(napi async work, pread loop, short read = reject); in-flight computation
shared between JS and the native round. Removed the now-unused sync
sha256Hex / sha256HexFile exports.
- copySandboxFile: fileIo.copyFile (old loop allocated per chunk and took a
short read for EOF).
Archive limits:
- checkOriginalSize: getOriginalSize failures still refuse the archive, now
with their own detail ("archive expansion size unavailable") and log line.
- planUnknownLengthFreeSpaceProbe extracted from the download write path.
- Documented why the compression ratio is checked on archive totals (zlib
exposes no per-entry sizes) and how that differs from archive_limits.h.
Configuration:
- C1: canonical config equivalence (defaults, trailing-slash strip + dedupe
of endpoints/queryUrls, key order ignored); used by hasJsRound and by
setNativeConfig (equivalent configs no longer bump generations or drop the
response cache).
- C2: syncNativeConfig rejects with NATIVE_CONFIG_CONFLICT once the host
called configure() in this process (local constant until error_codes.h
carries kNativeConfigConflict).
- SyncCoordinator: one configuration gate for preflight, host API and round,
with a consistently trimmed appKey.
- Fixed the misplaced setKv doc comment.
Tests: hypium suites for the probe plan, original-size refusal and C1;
constant parity now asserts kUnknownLengthFreeSpaceProbeBytes is parsed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci(e2e): build the iOS app once, share Android setup, tighten triggers
e2e-ios: a new e2e-ios-build job does pod install + xcodebuild (or the
cached-app bundle refresh) and prepares the local update artifacts once,
then uploads the .app and .e2e-artifacts/ios as a tarball (tar keeps the
executable bits upload-artifact drops). The core and native legs keep
their job id and "e2e-ios (core|native)" check names, download the build
and only run Detox. The e2e-cache action's trusted writer moves with the
work to e2e-ios-build; legs restore read-only.
e2e-android: the setup the three jobs duplicated (CLI checkout, node,
bun, caches, Java, KVM, deps, local link) is now the e2e-android-setup
composite action. The rn077 job keys its Gradle cache on the project
generator and the library build.gradle explicitly; it keeps a plain
`bun install` because the generated project has no lockfile to freeze.
Pushes trigger on master only (main does not exist).
Both: drop scripts/** and root bun.lock from the path filters (no e2e
build or test step reads them; package.json stays for codegenConfig)
and stop copying scripts/ into the linked package, which npm never
ships either.
Detox: the per-command --maxWorkers 1 moves into .detoxrc.js as an
Android-only configuration override (one AVD cannot boot twice). iOS
keeps its two workers on cloned simulators.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci: path-gate native unit tests, dedupe parity check, notify harmony-build
test.yml: a dorny/paths-filter job gates cpp-test (cpp/, android/jni/,
scripts/test-*) and the macOS ios-purge-restore-test (ios/, cpp/, its
script and fixtures). Skipped jobs report success, so required checks
and native-gate (which reads the workflow conclusion) stay satisfied.
The fuzz step runs apt-get update before installing clang.
lint.yml no longer runs check-native-spec-parity.js a second time; bun
lint already ends with it. harmony-build keeps its own run because it
does not run bun lint.
ci-failure-email also watches harmony-build. Dependabot now scans the
local composite actions, which pin SHAs as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci(publish): require native-required before publishing
The release commit must carry a green native-required status (posted by
native-gate.yml) before either publish job can run; pending or not yet
posted is waited out for up to 45 minutes. The full-history checkout
uses a blobless filter since only tags/commits/trees are read, and the
HAR comment now points at the files whitelist instead of the .npmignore.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(android): cheaper hashing, IO and JNI; canonical config equality
- Bundle hash computed once per process on its own thread (shared by JS
getBundleHash and the native round) instead of queueing on the download
executor behind cleanup/downloads.
- Downloads: 64KB reads with emitCompleteSegments; archive SHA-256 computed
while streaming (resume hashes the on-disk prefix first); full packages
hash index.bundlejs during unzip. Activation re-hash kept (tamper check).
64KB copy buffers in SafeZipFile/UpdateFileUtils.
- JNI: registration failures are logged; result classes, constructors and
field IDs cached once in JNI_OnLoad; a missing member logs and makes the
native throw instead of defaulting fields.
- Cleanup keeps current/last/running (String[] keep) and only renames doomed
entries into .trash under versionFilesLock; deletion runs after release.
- Native check response cache moved to its own SharedPreferences file
(migrated); round-incomplete breadcrumb cleared in the commit itself.
- getConstants: launch markers applied asynchronously; bundleSha256 cached
at launch resolution.
- One parseRunnableConfig for all config gates (appKey trimmed).
- Canonical config equivalence (shared contract) for the JS-round dedupe and
config-change invalidation; syncNativeConfig rejects with
NATIVE_CONFIG_CONFLICT once the host called PushyRuntime.configure().
- Shared OkHttpClient base; stale proguard comment fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: drop dead build/publish leftovers
- babel.config.js, @babel/core and @react-native/babel-preset: nothing at
the root transpiles with Babel (bun runs the tests, tsc type-checks);
every Example app carries its own babel config and preset.
- .npmignore: package.json `files` decides what ships and
check-packlist.js guards it; npm pack --dry-run lists the same 143
files before and after (only package.json's own size changes).
- scripts/verify-android-so.sh: unreferenced wrapper around
verify-android-so.js.
- scripts/prepublish.ts and its prepublishOnly hook: on CI it rewrote
package.json to the tag version (check-release-version.js already
fails the job unless they match) and re-ran verify-android-so.js
(publish.yml runs it explicitly in both publish jobs). Its local
branch only served the also-removed publish:local script; releases go
through publish.yml.
- nanoid: src is inlining its id generator.
generate-flow-vectors.ts takes URL from node:url: with prepublish.ts
gone the program's global URL (react-native's declaration) no longer
type-checks as a node:fs/node:url path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ios): canonical config equality, lock-free digest check, bounded host waits
- Config changes: persistConfiguration and hasJsRound compare configs by
their canonical form (defaults filled, key order ignored, trailing '/'
stripped and endpoints/queryUrls deduplicated). An equivalent config is a
no-op; a real change bumps its own pushyNativeConfigGeneration, which the
round commit checks next to pushyResetGeneration (now reset-only).
- syncNativeConfig rejects with NATIVE_CONFIG_CONFLICT once the host called
+configure:completion: in this process.
- One PushyLoadRunnableConfig gate for crash-hold install, round start,
prepareBundle and the round itself (same disabled/appKey rules).
- switchVersion hashes the bundle outside the state lock (+bundleURL takes
it on the main thread); under the lock it re-checks the record and the
bundle's inode/size/mtime before switching.
- prepareBundle runs the round on its own queue and waits a bounded time:
new +prepareBundleWithTimeout:completion:, the old selector waits at most
30 minutes. Expiry reports failed/timeout.
- tvOS purged-version restore: budget counts from process start, skipped
when offline (nw_path_monitor) or with < 2s left, and never hashes the
embedded bundle on that path.
- Encoded constants decode lazily into a byte buffer; the URL regex is
compiled once; one long-lived NSURLSession; OS version from NSProcessInfo
instead of main-thread-only UIDevice; the endpoint loop is shared.
- podspec: tvOS deployment target from react-native-tvos (or Expo), 15.1
fallback, instead of the iOS one; link Network on tvOS.
- Purge-restore harness: late launch, offline and spent-budget cases plus
canonical config equality.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: type-check against react-native 0.85
The root devDependency was still react-native 0.73 while the e2e app
(and every supported host) runs 0.85; align react-native and
@react-native/typescript-config with Example/e2etest (0.85.2). react
and react-test-renderer stay on 18 for the bun:test render tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(harmony): mirror NATIVE_CONFIG_CONFLICT from error_codes.h
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: move the design documents into docs/
NATIVE_CHECKUPDATE_DESIGN, NATIVE_CHECK_FOLLOWUPS, BUNDLEHASH_MIGRATION
and NATIVE_HOST_API_MIGRATION leave the repository root. The README
links and the two path-style mentions (check-native-spec-parity.js,
harmony-build.yml) follow; code comments cite them by name only and
need no change. check-packlist.js now forbids docs/ outright instead of
matching the old root file names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: import URL from node:url for RN 0.85 type declarations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(ios): move the native round into RCTPushyOrchestrator.mm
Pure move, no behavior change. RCTPushyOrchestrator (its statics, the
crash-hold handler and the HTTP/config helpers) now lives in its own file;
the helpers both files need (state lock, defaults, state transitions, key
constants, PushyHasCompletedVersionAtPath, bundle hash) switch from static
to PUSHY_HIDDEN and are declared in the private RCTPushyInternal.h, together
with PushyType, the private RCTPushy interface and the orchestrator
interface. The podspec keeps that header out of the public umbrella.
The purge-restore harness extracts from both sources (one definition across
them) and accepts PUSHY_HIDDEN definitions like static ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(ios): use error_codes.h for NATIVE_CONFIG_CONFLICT; guard the orchestrator in the packlist
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: state that host configure() requires nativeConfigSource: 'native'
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* revert: keep the documented native-then-JS config handover
Host configure() followed by a JS takeover in the default 'javascript'
mode is a documented, supported flow (last completed write wins).
Rejecting it with NATIVE_CONFIG_CONFLICT would break existing
integrations. The underlying harm is already gone: equivalent configs
(C1) no longer count as a change, so they neither drop the response
cache nor cancel in-flight rounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(e2e-harmony): cover the package diff and bundleHash identity
The Harmony suite never exercised the package diff (patching against the
bundle embedded in the hap), and nothing checked that the bundleHash the
client reports matches that bundle. Both paths now read the rawfile via
an fd, so cover them on device:
- prepare builds v3 for harmony and, once the hap exists, diffs it with
hdiffFromApp; build-harmony-e2e.sh reruns that step after assembleHap
- the local server serves pdiff v3 to harmony and records the last
bundleHash each platform sent (/control/last-bundle-hash)
- the suite asserts bundleHash == sha256(hap rawfile bundle) and walks
v2 -> pdiff v3 -> v2-track v4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: address CodeRabbit review on #654
- ios: run host prepareBundle calls on a concurrent queue so each
timeout counts from its own call (a serial queue queued a timed call
behind an earlier call's full wait)
- android: a failed trash purge is logged, not a failed cleanup task
- android: restore the configure() note that JS writes can replace the
config unless nativeConfigSource is 'native'
- ci: run the C++ parity job when the TS reference or vector generator
changes
- deps: react / react-test-renderer 19.2.3 to match react-native 0.85
- js: clarify when the native check cache is marked exhausted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: share one C1 config-equivalence vector file across all four platforms
cpp/update_flow_core/tests/config-equivalence-vectors.json (58 cases, not
packed: !cpp/**/tests) is now the single contract for native config
equivalence. Each case is checked in both argument orders by:
- JS + Harmony: src/__tests__/configEquivalenceVectors.test.ts (Harmony's
UpdateContext.ts evaluated in a vm with imports stripped)
- Android: ConfigEquivalenceTest (locates the file by walking up from the
module dir, works in-repo and from node_modules)
- iOS: the purge-restore harness's config_equivalence case
The vectors exposed two outliers, fixed to the agreed rule:
- JS did not dedupe non-string entries of endpoints/queryUrls
([1,1] stayed [1,1]); Android, iOS and Harmony dedupe by JSON value.
- Android accepted trailing content after the object ('{"a":1} x' and two
concatenated objects) because JSONTokener.nextValue() stops after the
first value; JS, iOS and Harmony reject it as invalid JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(e2e): record the bundleHash of every checkUpdate, not the last non-empty one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1 parent b33cc5c commit 7e4e095
100 files changed
Lines changed: 5378 additions & 3295 deletions
File tree
- .github
- actions
- e2e-android-setup
- e2e-cache
- workflows
- Example
- e2etest
- e2e/harmony
- scripts
- harmony_use_pushy/e2e
- android
- jni
- lib
- arm64-v8a
- armeabi-v7a
- x86_64
- x86
- src
- main/java/cn/reactnative/modules/update
- test/java/cn/reactnative/modules/update
- cpp
- patch_core
- tests
- update_flow_core/tests
- docs
- harmony/pushy/src
- main
- cpp
- ets
- test
- ios/RCTPushy
- scripts
- tests/ios-purge-restore
- src
- __tests__
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
21 | | - | |
| 20 | + | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
29 | | - | |
| 28 | + | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
7 | 10 | | |
8 | 11 | | |
9 | 12 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
| 10 | + | |
8 | 11 | | |
9 | 12 | | |
10 | 13 | | |
| |||
0 commit comments