codepush 0.2.8
codepush: ^0.2.8 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.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.