codepush 0.3.7
codepush: ^0.3.7 copied to clipboard
Ship Dart fixes to installed Flutter apps without an app-store review. Signed patches, staged rollout, crash-safe rollback. Android needs no forked engine.
0.3.7 #
- Android: finds the base library for the ABI the app actually runs as. An APK carrying every ABI, installed as 32-bit on a phone that also runs 64-bit (a plugin with 32-bit-only native code does this), got the arm64 library, so its patch built a library the app could not load and the app stayed unpatched. Needed for APK releases built for every ABI.
0.3.6 #
- Android: takes a patch for an app bundle (AAB) release, which carries a diff for every ABI; the phone applies the one made for its own library. Works with Play's split APKs. Needs codepush_protocol 0.1.2.
0.3.5 #
- iOS: takes a patch as a delta from the patch it already runs (tens of KB instead of ~2.5 MB), when the server offers one signed with the app key. Anything else falls back to the full patch.
- iOS status file records the patch model, so
verifyno longer warns that a link patch "replaced 0 functions". CodePush.update()andCodePush.currentPatchNumber(), both platforms.auto_update: falsein codepush.yaml turns the automatic check off.- One check at a time: a manual
update()during the automatic check waits for it instead of racing it for the staging file. - A release with no patch yet (manifest 404) is reported as up to date, not as a failed check on every launch.
- Android: accepts diff patches against the library already inside the APK. A one-screen change goes from ~14 MB to a few hundred KB. The CLI sends a diff only to releases built with this version or later.
0.3.4 #
- Android: accepts gzip-compressed patches (about 60% smaller downloads). The CLI compresses only for releases built with this version or later.
- Android: a healthy patch was quarantined on every patched launch, because the boot decision ran twice. It now runs once per process.
0.3.3 #
- Leaving the app (home, app switcher) during a patch's first launch no longer counts as a crash: the patch is kept if nothing threw until then.
0.3.2 #
- Reports carry the phone model and OS version.
- A rolled-back patch is reported with the full error, stack and the engine's last load steps.
- Errors thrown while a patch runs, after it was confirmed healthy, are reported (at most three a launch).
- A patch that fails to download or verify is reported with the reason.
0.3.1 #
- A patch that does not come up healthy is rolled back on the very next launch (needs a release built with the matching CodePush engine).
- A rolled-back patch is reported with the first error it hit and where, so the panel can show why it failed on each device.
0.3.0 #
iOS patches use the link model and the CodePush server.
- A patch is data: the release keeps its machine code for every unchanged
function and the engine runs the changed ones as bytecode. Staged under
codepush_link/, loaded by the engine before the first frame. - Manifests are signed by the server; devices verify against the key compiled into the release.
codepush_iosanddynamic_modulesare no longer needed or used.
0.2.8 #
A patch that crashes the app is now actually rolled back.
Probation used to end on the first frame. A patch that rendered a splash and then crashed while mounting the next screen cleared the breadcrumb on that frame, so every later launch treated it as healthy and crashed again — the app was permanently broken by a patch, and the mechanism meant to prevent exactly that never fired, because by its own measure nothing had gone wrong.
Probation now lasts healthyAfter (8s by default) past the first frame, which
covers early navigation and the first real screen. Two launches that fail
inside that window drop the patch and the app returns to shipped code.
The cost is a false positive: force-quitting inside the window twice in a row loses that patch. A lost patch is an inconvenience; an app that cannot start is not.
0.2.7 #
The check record says whether this process's timers are alive.
It is the first thing worth knowing when a check never finishes, and nothing else reports it: a platform channel answering proves the message loop works and says nothing about timers, which every timeout in the check is built on. If they are dead the check does not fail, it hangs.
0.2.6 #
The update check records running as well as scheduled and its decision, and
has an outer deadline so it can never sit unfinished forever.
scheduled and a real decision were not enough to debug this on a device: a
check that was never reached and a check that hung produced the same record,
and they have completely different causes. The deadline covers what the inner
timeouts cannot — a platform channel that never answers, or a socket that
neither connects nor errors — and turns a permanent hang into a reported
failure.
0.2.5 #
The iOS update check no longer uses a Timer, because a Timer does not fire on
a launch where a patch applied.
Measured on a device: still unfired 90 seconds in, whether created before or after the patch was applied, while post-frame callbacks on the same isolate ran normally — including the boot report, which does network I/O. Async and the event loop are fine; applying a patch appears to take the isolate's timer queue with it. The consequence was that after the first patch a device never checked again: it ran that patch forever while later ones sat published, signed and fetchable, and nothing on the device or the server said so.
The check now runs from a post-frame callback, which the engine's frame scheduling drives rather than the Dart timer queue. This gives up the four-second delay the Timer provided; running after the first frame still keeps it off the startup path, and the check is one small GET.
0.2.3 and 0.2.4 were two wrong diagnoses of this same symptom.
0.2.4 #
0.2.3 named the right rule and fixed the wrong cause. The update check is now scheduled before the patch is applied, not after.
A timer created after IosPatchLoader.bootstrap() never fires on a launch where
a patch applied — measured on a device, still unfired 90 seconds in, while
post-frame callbacks on the same isolate ran normally. Capturing the config
instead of reading a static (0.2.3) was not enough, because the callback was
never reached at all. So every launch after the first patch skipped the check,
and the device ran that patch forever while later ones sat published, signed and
fetchable.
0.2.3 #
A patch could permanently switch off the update check. Once a patch was applied, every later launch skipped the check: the startup timer's callback read a static field in this library, and applying a patch can reinitialise the libraries it replaces, so that field read back null and the callback returned before doing anything. The device kept running the first patch it ever received while later patches sat published, signed and fetchable. Found on a real device, where patch 2 never arrived twice in a row.
The check now runs from the config captured when it was scheduled, so nothing on that path depends on static state outliving a patch. A patch must never be able to disable the mechanism that would replace it.
A check that is due also records itself as scheduled, so a check that never
runs is distinguishable from one that ran and found nothing — those two were
identical from outside the device, which is why this went unnoticed.
0.2.2 #
The device now records what the update check decided, not only what the boot
loader did. Those are different things and only the second was ever written
down: the check is where a patch is accepted or refused, and its one output was
a debugPrint that does not reach a release build's console. So on a real
device, "I published a patch and nothing changed" had no answer — the boot
status kept correctly reporting the previous patch while the new one was being
declined for an invisible reason.
0.2.1 #
The device status file now records which patch a launch acted on. The outcome
alone could not answer the two questions actually asked of a device — "did my
new patch land" and "did the rollback take" — because a before/after pair both
report applied with the same replaced count.
0.2.0 #
Fixes the default patch contract, which named five packages the first app to
use CodePush happened to depend on: go_router, intl, dio, shared_preferences
and get. The compiler resolves every library a contract names, so any project
without one of them failed an iOS release with "Couldn't resolve the package".
They were also redundant — codepush release ios already adds every library
the app links. The contract now lists framework libraries only.
0.1.0 #
First release.