@_nazmiforreal/flutter-ota 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/flutter-ota-linux-x64 +0 -0
- package/bin/flutter-ota.js +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +754 -0
- package/dart-src/packages/cli-tools/.dart_tool/version +1 -0
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +22 -1
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +17 -8
- package/dart-src/packages/cli-tools/pubspec.lock +636 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +7 -7
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +2 -1
- package/dart-src/root/analysis_options.yaml +3 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +1 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +24 -5
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +20 -0
- package/dart-src/root/lib/flutter_ota_kit.dart +31 -11
- package/dart-src/root/lib/src/aws_update_source.dart +1 -0
- package/dart-src/root/lib/src/cloudflare_update_source.dart +1 -0
- package/dart-src/root/lib/src/ota_progress_overlay.dart +243 -296
- package/dart-src/root/lib/src/patcher_channel.dart +14 -1
- package/dart-src/root/lib/src/pkg/plugin_core/src/request_update_bundle_state.dart +2 -1
- package/dart-src/root/lib/src/pkg/postgres/src/postgres_types.dart +2 -1
- package/dart-src/root/lib/src/pocketbase_update_source.dart +1 -0
- package/dart-src/root/lib/src/postgres_update_source.dart +1 -0
- package/dart-src/root/lib/src/shared_update_check.dart +30 -2
- package/dart-src/root/lib/src/supabase_update_source.dart +1 -0
- package/dart-src/root/pubspec.yaml +14 -14
- package/package.json +1 -1
package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt
CHANGED
|
@@ -613,14 +613,22 @@ internal class PatchManager(
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
private fun extractBaseAssetsIfNeeded(): Int {
|
|
616
|
-
//
|
|
616
|
+
// Store the base APK's flutter_assets as a single archive for rollback.
|
|
617
|
+
// We keep ONLY the compressed archive (not an uncompressed mirror dir):
|
|
618
|
+
// the previous implementation left both `assets/0/` (uncompressed) AND
|
|
619
|
+
// `assets/0/flutter_assets.apk` (zip) on disk — two full copies of every
|
|
620
|
+
// bundled asset. The uncompressed copy is never read (rollback re-extracts
|
|
621
|
+
// from the archive), so it was pure waste.
|
|
617
622
|
if (!baseAssetsArchive.exists()) {
|
|
618
623
|
baseAssetsArchive.parentFile?.mkdirs()
|
|
624
|
+
val tmp = File(assetsHistoryDir, "0.tmp")
|
|
619
625
|
try {
|
|
620
|
-
copyInstalledFlutterAssets(
|
|
621
|
-
writeFlutterAssetsArchive(
|
|
626
|
+
copyInstalledFlutterAssets(tmp)
|
|
627
|
+
writeFlutterAssetsArchive(tmp, baseAssetsArchive)
|
|
622
628
|
} catch (e: Exception) {
|
|
623
629
|
Log.w(TAG, "failed to extract base assets", e)
|
|
630
|
+
} finally {
|
|
631
|
+
tmp.deleteRecursively()
|
|
624
632
|
}
|
|
625
633
|
}
|
|
626
634
|
return 0
|
|
@@ -998,8 +1006,19 @@ internal class PatchManager(
|
|
|
998
1006
|
meta.put("assetsRef", assetsRef)
|
|
999
1007
|
archiveCurrentPatchToHistory(meta)
|
|
1000
1008
|
} else {
|
|
1001
|
-
// First patch ever
|
|
1002
|
-
|
|
1009
|
+
// First patch ever.
|
|
1010
|
+
//
|
|
1011
|
+
// Only extract the base APK's flutter_assets (as a rollback
|
|
1012
|
+
// reference) when this patch actually ships assets. A Dart-only
|
|
1013
|
+
// (code-only) patch never overlays assets, so extracting the
|
|
1014
|
+
// whole base asset tree here is pure waste — it was the main
|
|
1015
|
+
// cause of a first code-patch ballooning app storage by 100s of
|
|
1016
|
+
// MB. Code-only patches load assets straight from the APK.
|
|
1017
|
+
assetsRef = if (finalAssets != null) {
|
|
1018
|
+
extractBaseAssetsIfNeeded()
|
|
1019
|
+
} else {
|
|
1020
|
+
0
|
|
1021
|
+
}
|
|
1003
1022
|
meta.put("assetsRef", assetsRef)
|
|
1004
1023
|
}
|
|
1005
1024
|
|
package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt
CHANGED
|
@@ -30,6 +30,11 @@ internal object PatcherConfig {
|
|
|
30
30
|
const val KEY_CRASH_COUNT = "crash_count"
|
|
31
31
|
const val KEY_PATCH_LOADING = "patch_loading"
|
|
32
32
|
|
|
33
|
+
/** Random UUID generated on first read and persisted; seeds the rollout
|
|
34
|
+
* cohort on the Dart side. Survives patch updates and base-APK upgrades —
|
|
35
|
+
* only reset by uninstall / `pm clear`, like the rest of these prefs. */
|
|
36
|
+
private const val KEY_DEVICE_ID = "device_install_id"
|
|
37
|
+
|
|
33
38
|
/** PID written at [com.flutter_patcher.flutter_patcher.CrashGuard.markBooting]; consumed
|
|
34
39
|
* next cold start to look up `ActivityManager.getHistoricalProcessExitReasons` (API 30+).
|
|
35
40
|
* Unused on API < 30 — that path uses the naive "patch_loading=true ⇒ crash" rule. */
|
|
@@ -87,6 +92,21 @@ internal object PatcherConfig {
|
|
|
87
92
|
fun prefs(context: Context): SharedPreferences =
|
|
88
93
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
89
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Stable per-install device identifier (generated once, persisted).
|
|
97
|
+
*
|
|
98
|
+
* Used as the seed for server-side rollout-cohort hashing so a device
|
|
99
|
+
* lands in a consistent cohort across releases. Never leaves the device —
|
|
100
|
+
* the app sends only the derived 0-999 cohort number.
|
|
101
|
+
*/
|
|
102
|
+
fun deviceInstallId(context: Context): String {
|
|
103
|
+
val existing = prefs(context).getString(KEY_DEVICE_ID, null)
|
|
104
|
+
if (!existing.isNullOrEmpty()) return existing
|
|
105
|
+
val fresh = java.util.UUID.randomUUID().toString()
|
|
106
|
+
prefs(context).edit().putString(KEY_DEVICE_ID, fresh).apply()
|
|
107
|
+
return fresh
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
internal fun encodeLoaderFieldCandidates(fields: List<String>): String {
|
|
91
111
|
val normalized = fields
|
|
92
112
|
.map { it.trim() }
|
|
@@ -756,7 +756,9 @@ class FlutterPatcher {
|
|
|
756
756
|
///
|
|
757
757
|
/// Call [restart] after a successful rollback to activate the previous patch.
|
|
758
758
|
static Future<RollbackOutcome> rollbackToPrevious() async {
|
|
759
|
-
if (_notAndroidGuard('rollbackToPrevious'))
|
|
759
|
+
if (_notAndroidGuard('rollbackToPrevious')) {
|
|
760
|
+
return RollbackOutcome.fallbackToBase;
|
|
761
|
+
}
|
|
760
762
|
try {
|
|
761
763
|
final outcomeName = await PatcherChannel.rollbackToPrevious();
|
|
762
764
|
return RollbackOutcome.values.byName(outcomeName);
|
|
@@ -815,8 +817,9 @@ class FlutterPatcher {
|
|
|
815
817
|
final showOverlay = showUpdateUi && result.shouldForceUpdate;
|
|
816
818
|
final fromVersion = await currentVersion;
|
|
817
819
|
final patchHash = result.patch!.md5;
|
|
818
|
-
final shortHash =
|
|
819
|
-
|
|
820
|
+
final shortHash = patchHash.isNotEmpty
|
|
821
|
+
? patchHash.substring(0, patchHash.length.clamp(0, 8))
|
|
822
|
+
: null;
|
|
820
823
|
|
|
821
824
|
// Extract channel/platform from whichever config is active
|
|
822
825
|
String? channel;
|
|
@@ -878,7 +881,9 @@ class FlutterPatcher {
|
|
|
878
881
|
if (error != null &&
|
|
879
882
|
deterministic.contains(error) &&
|
|
880
883
|
patchVersion.isNotEmpty) {
|
|
881
|
-
_log(
|
|
884
|
+
_log(
|
|
885
|
+
'applyUpdate: patch failed (${error.name}), blacklisting $patchVersion',
|
|
886
|
+
);
|
|
882
887
|
try {
|
|
883
888
|
await PatcherChannel.reportApplyFailure(
|
|
884
889
|
version: patchVersion,
|
|
@@ -889,15 +894,22 @@ class FlutterPatcher {
|
|
|
889
894
|
_log('applyUpdate: failed to blacklist $patchVersion: $e', s);
|
|
890
895
|
}
|
|
891
896
|
} else {
|
|
892
|
-
_log(
|
|
893
|
-
|
|
897
|
+
_log(
|
|
898
|
+
'applyUpdate: patch failed (${error?.name}); transient, not '
|
|
899
|
+
'blacklisting (will retry next check)',
|
|
900
|
+
);
|
|
894
901
|
}
|
|
895
902
|
}
|
|
896
903
|
|
|
897
904
|
if (applied.ok && result.shouldForceUpdate) {
|
|
898
905
|
handle?.end();
|
|
899
|
-
//
|
|
900
|
-
|
|
906
|
+
// Let the overlay finish its minimum display time + success dwell and
|
|
907
|
+
// fully unmount before killing the process. The old fixed 3s wait could
|
|
908
|
+
// restart while the install screen was still visible, because a fast
|
|
909
|
+
// download keeps the overlay up until the 7s minimum duration elapses.
|
|
910
|
+
await (handle?.dismissed ??
|
|
911
|
+
Future<void>.delayed(const Duration(seconds: 3)))
|
|
912
|
+
.timeout(const Duration(seconds: 12), onTimeout: () {});
|
|
901
913
|
await restart();
|
|
902
914
|
} else {
|
|
903
915
|
handle?.end(hasError: !applied.ok, errorText: applied.message);
|
|
@@ -932,7 +944,9 @@ class FlutterPatcher {
|
|
|
932
944
|
if (showUpdateUi) {
|
|
933
945
|
OtaOverlayManager.instance.showRollbackToast(
|
|
934
946
|
message: 'Update reverted — using previous version',
|
|
935
|
-
previousVersion: outcome == RollbackOutcome.success
|
|
947
|
+
previousVersion: outcome == RollbackOutcome.success
|
|
948
|
+
? 'previous'
|
|
949
|
+
: 'base',
|
|
936
950
|
);
|
|
937
951
|
}
|
|
938
952
|
await restart();
|
|
@@ -953,7 +967,10 @@ class FlutterPatcher {
|
|
|
953
967
|
final patchVersion = result.patch!.version;
|
|
954
968
|
final patchMd5 = result.patch!.md5;
|
|
955
969
|
try {
|
|
956
|
-
if (await PatcherChannel.isVersionBlacklisted(
|
|
970
|
+
if (await PatcherChannel.isVersionBlacklisted(
|
|
971
|
+
patchVersion,
|
|
972
|
+
md5: patchMd5,
|
|
973
|
+
)) {
|
|
957
974
|
_log('checkAndApplyUpdates: $patchVersion is blacklisted, skip');
|
|
958
975
|
return null;
|
|
959
976
|
}
|
|
@@ -1039,7 +1056,10 @@ class FlutterPatcher {
|
|
|
1039
1056
|
///
|
|
1040
1057
|
/// When [md5] is provided, the match uses both version and md5. Without md5,
|
|
1041
1058
|
/// any entry with the same version is considered a match.
|
|
1042
|
-
static Future<bool> isVersionBlacklisted(
|
|
1059
|
+
static Future<bool> isVersionBlacklisted(
|
|
1060
|
+
String version, {
|
|
1061
|
+
String md5 = '',
|
|
1062
|
+
}) async {
|
|
1043
1063
|
if (_notAndroidGuard('isVersionBlacklisted')) return false;
|
|
1044
1064
|
try {
|
|
1045
1065
|
return await PatcherChannel.isVersionBlacklisted(version, md5: md5);
|