@_nazmiforreal/flutter-ota 0.1.25 → 0.1.27
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 +12 -11
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
- package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
- package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
- package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
- package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
- package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
- package/dart-src/root/README.md +394 -0
- package/dart-src/root/analysis_options.yaml +21 -0
- package/dart-src/root/android/build.gradle +116 -0
- package/dart-src/root/android/consumer-rules.pro +23 -0
- package/dart-src/root/android/settings.gradle +1 -0
- package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
- package/dart-src/root/bin/pack.dart +182 -0
- package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
- package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
- package/dart-src/root/lib/src/aws_update_source.dart +97 -0
- package/dart-src/root/lib/src/blacklist.dart +54 -0
- package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
- package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
- package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
- package/dart-src/root/lib/src/io.dart +48 -0
- package/dart-src/root/lib/src/io_stub.dart +19 -0
- package/dart-src/root/lib/src/ota_progress_overlay.dart +987 -0
- package/dart-src/root/lib/src/pack/pack.dart +434 -0
- package/dart-src/root/lib/src/patch_info.dart +12 -0
- package/dart-src/root/lib/src/patcher_channel.dart +160 -0
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
- package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
- package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
- package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
- package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
- package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
- package/dart-src/root/lib/src/shared_update_check.dart +131 -0
- package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
- package/dart-src/root/pubspec.yaml +54 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +17 -13
- package/scripts/vendor.sh +22 -7
- package/dart-src/packages/core/CHANGELOG.md +0 -21
- package/dart-src/packages/core/README.md +0 -23
- package/dart-src/packages/core/example/lib/main.dart +0 -69
- package/dart-src/packages/core/pubspec.yaml +0 -13
- package/dart-src/packages/core/test/rollout_test.dart +0 -135
- package/dart-src/packages/core/test/semver_test.dart +0 -55
- package/dart-src/packages/core/test/semver_vectors.json +0 -1
- package/dart-src/packages/core/test/types_test.dart +0 -131
- package/dart-src/packages/core/test/uuid_test.dart +0 -40
- package/dart-src/packages/core/tool/dbg.dart +0 -7
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/CHANGELOG.md +0 -23
- package/dart-src/plugins/aws/LICENSE +0 -21
- package/dart-src/plugins/aws/README.md +0 -28
- package/dart-src/plugins/aws/example/lib/main.dart +0 -60
- package/dart-src/plugins/aws/pubspec.yaml +0 -30
- package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
- package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
- package/dart-src/plugins/cloudflare/LICENSE +0 -21
- package/dart-src/plugins/cloudflare/README.md +0 -25
- package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
- package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
- package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
- package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
- package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
- package/dart-src/plugins/plugin-core/LICENSE +0 -21
- package/dart-src/plugins/plugin-core/README.md +0 -21
- package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
- package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
- package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
- package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
- package/dart-src/plugins/pocketbase/LICENSE +0 -21
- package/dart-src/plugins/pocketbase/README.md +0 -54
- package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
- package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
- package/dart-src/plugins/postgres/LICENSE +0 -21
- package/dart-src/plugins/postgres/README.md +0 -29
- package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
- package/dart-src/plugins/postgres/pubspec.yaml +0 -26
- package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
- package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
- package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
- package/dart-src/plugins/supabase/LICENSE +0 -21
- package/dart-src/plugins/supabase/README.md +0 -53
- package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
- package/dart-src/plugins/supabase/pubspec.yaml +0 -27
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
- /package/dart-src/{packages/core → root}/LICENSE +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# flutter_ota_kit
|
|
2
|
+
|
|
3
|
+
**English** | [简体中文](README-zh.md)
|
|
4
|
+
|
|
5
|
+
[](https://pub.dev/packages/flutter_ota_kit)
|
|
6
|
+
[](https://flutter.dev)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
**Open-source code push for Flutter Android.**
|
|
10
|
+
|
|
11
|
+
Ship Dart code and asset hotfixes over the air — no store release, no
|
|
12
|
+
forced vendor lock-in, no per-month fees. Patches live on **your** backend
|
|
13
|
+
(Supabase, Postgres, Cloudflare, AWS, or your own object storage) and load
|
|
14
|
+
on the next cold start.
|
|
15
|
+
|
|
16
|
+
If you've used [Shorebird](https://shorebird.dev/),
|
|
17
|
+
[CodePush](https://learn.microsoft.com/en-us/appcenter/distribution/codepush/),
|
|
18
|
+
or [Expo EAS Update](https://docs.expo.dev/eas-update/introduction/) —
|
|
19
|
+
flutter_ota_kit is the same OTA update model for Flutter Android, MIT-licensed,
|
|
20
|
+
with your choice of cloud backend.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## How it compares
|
|
27
|
+
|
|
28
|
+
| | flutter_ota_kit | Shorebird | CodePush (React Native) |
|
|
29
|
+
|-----------------------|--------------------------------------------------|-------------------------------|-------------------------------|
|
|
30
|
+
| **Framework** | Flutter | Flutter | React Native |
|
|
31
|
+
| **Platforms** | Android | Android + iOS | Android + iOS (retired 2025) |
|
|
32
|
+
| **Hosting** | Your backend or your own CDN — see [Backends](doc/backends.md) | Shorebird cloud (managed) | AppCenter cloud (deprecated) |
|
|
33
|
+
| **Update scope** | Dart AOT + Flutter assets | Dart code (engine-level diff) | JS bundle |
|
|
34
|
+
| **Takes effect** | Next cold start | Next restart | Next restart |
|
|
35
|
+
| **Cost** | Free (MIT) | Free tier + paid plans | — |
|
|
36
|
+
| **Backend flexibility** | Supabase / Postgres / Cloudflare / AWS / bring-your-own | Cloud-managed only | — |
|
|
37
|
+
| **Forced updates** | Yes — built-in progress UI | Yes | Yes |
|
|
38
|
+
| **Crash rollback** | Automatic + bad-patch blacklist | Yes | Yes |
|
|
39
|
+
| **Signing** | Ed25519 (Android 13+) + MD5 | Yes | — |
|
|
40
|
+
|
|
41
|
+
**Choose Shorebird** if you need iOS support today or want a fully managed cloud.
|
|
42
|
+
|
|
43
|
+
**Choose flutter_ota_kit** if you need OTA on infrastructure you control —
|
|
44
|
+
enterprise apps, regional distribution, air-gapped deployments, or non-Play
|
|
45
|
+
distribution channels. Bring your own backend, your own CDN, your own auth.
|
|
46
|
+
|
|
47
|
+
> Google Play and some stores restrict downloading executable code at runtime.
|
|
48
|
+
> flutter_ota_kit targets self-controlled, enterprise, or permissive
|
|
49
|
+
> distribution channels. Verify your channel's policy before shipping.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **OTA code push** — replace the Dart AOT `libapp.so` and Flutter assets on the
|
|
56
|
+
next cold start, with MD5 + Ed25519 integrity checks.
|
|
57
|
+
- **Five cloud backends** — Supabase (fully automated), Postgres, Cloudflare (D1 + R2),
|
|
58
|
+
AWS (S3), **PocketBase** (single-binary self-hosted). Or bring your own CDN.
|
|
59
|
+
- **Crash rollback** — automatic on boot failure with a bad-patch blacklist.
|
|
60
|
+
Configurable via `maxCrashCount` and `verifyAfter`.
|
|
61
|
+
- **Forced updates with built-in UI** — opt into zero-click updates with
|
|
62
|
+
`autoApplyUpdates: true`; the SDK shows a download spinner, progress bar, and
|
|
63
|
+
the server's OTA message during the install. No UI code from you.
|
|
64
|
+
- **Staged rollout** — server-side cohort math (per-mille, target-cohorts) lets
|
|
65
|
+
you ship 1% → 5% → 20% → 50% → 100% safely.
|
|
66
|
+
- **Tooling included** — `pack` CLI (builds patches from release APKs),
|
|
67
|
+
`flutter_ota_kit console` (web-based admin UI for the sidecar server),
|
|
68
|
+
runtime diagnostics, sample app.
|
|
69
|
+
- **No native plugin churn** — the patch system uses the Android
|
|
70
|
+
`ContentProvider` to stage files; no platform channel gymnastics, no
|
|
71
|
+
per-vendor SDKs to upgrade.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Try it in 5 minutes (no server needed)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/HYPER12755/flutter-ota-kit.git
|
|
79
|
+
cd flutter_ota_kit/example
|
|
80
|
+
flutter pub get
|
|
81
|
+
flutter build apk --release
|
|
82
|
+
flutter install
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
1. Launch the app — it shows the **original** `assets/patch_demo.png`.
|
|
86
|
+
2. Tap **Apply patch**.
|
|
87
|
+
3. Swipe the app away from recents and reopen.
|
|
88
|
+
4. The image has changed — the asset patch took effect.
|
|
89
|
+
5. Tap **Rollback** → restart → original image is back.
|
|
90
|
+
|
|
91
|
+
The example bundles a precompiled `patch.zip`. Everything runs offline on
|
|
92
|
+
the device. For HTTP-based testing with a real backend, see
|
|
93
|
+
[Getting Started](doc/getting-started.md) or the
|
|
94
|
+
[Beginner Guide](doc/beginner-guide.md) (the human-narrated walkthrough).
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
| Item | Requirement |
|
|
101
|
+
|----------------------------|--------------------------------------------------------------------|
|
|
102
|
+
| **Platform** | Android only (iOS / macOS / Windows / Linux / Web are no-op) |
|
|
103
|
+
| **Dart SDK** | `>=3.13.0 <4.0.0` |
|
|
104
|
+
| **Flutter** | `>=3.47.0` |
|
|
105
|
+
| **Android `minSdk`** | 24 |
|
|
106
|
+
| **Android `compileSdk`** | 36 |
|
|
107
|
+
| **ABI** | `armeabi-v7a` / `arm64-v8a` / `x86_64` |
|
|
108
|
+
| **NDK** | 27.0.12077973+ |
|
|
109
|
+
| **AGP** | 8.11.1+ (including AGP 9.x) |
|
|
110
|
+
| **Kotlin** | 2.2.20+ (or AGP 9 built-in Kotlin) |
|
|
111
|
+
| **Java / JVM** | 17 |
|
|
112
|
+
|
|
113
|
+
On iOS / macOS / Windows / Linux / Web, every API is safe to call but is a
|
|
114
|
+
**no-op** — the plugin logs a one-time warning and returns safe defaults.
|
|
115
|
+
This makes it safe to ship `flutter_ota_kit` in cross-platform code without
|
|
116
|
+
guarding every call site.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Quick start
|
|
121
|
+
|
|
122
|
+
### 1. Install
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# pubspec.yaml
|
|
126
|
+
dependencies:
|
|
127
|
+
flutter_ota_kit: ^0.1.10
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. Initialize
|
|
131
|
+
|
|
132
|
+
Call `setupFlutterOta()` (or the bare `FlutterPatcher.init()`) before `runApp()`:
|
|
133
|
+
|
|
134
|
+
```dart
|
|
135
|
+
import 'package:flutter/material.dart';
|
|
136
|
+
import 'package:flutter_ota_kit/flutter_ota_kit.dart';
|
|
137
|
+
import 'flutter_ota_kit_setup.dart'; // generated by `flutter-ota init <backend>`
|
|
138
|
+
|
|
139
|
+
Future<void> main() async {
|
|
140
|
+
WidgetsFlutterBinding.ensureInitialized();
|
|
141
|
+
await setupFlutterOta(); // configures the backend + zero-click forced updates
|
|
142
|
+
runApp(const FlutterOtaApp(child: MyApp()));
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`FlutterOtaApp` is the zero-code wrapper that gives the SDK an
|
|
147
|
+
`Overlay` to render the built-in forced-update progress UI into. The user
|
|
148
|
+
sees a download spinner + progress bar + the server's OTA message during
|
|
149
|
+
the install — no UI from you.
|
|
150
|
+
|
|
151
|
+
Disable the overlay app-wide with `FlutterPatcher.showUpdateUi = false`
|
|
152
|
+
(or `FlutterOtaApp(showUpdateUi: false)`), or host it yourself by
|
|
153
|
+
assigning `MaterialApp.navigatorKey = FlutterPatcher.navigatorKey`.
|
|
154
|
+
|
|
155
|
+
### 3. Build a patch
|
|
156
|
+
|
|
157
|
+
Rebuild the release APK, then run `pack` against the new APK to produce
|
|
158
|
+
`dist/patch.zip` and `dist/manifest.json`:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
dart run flutter_ota_kit:pack \
|
|
162
|
+
--apk build/app/outputs/flutter-apk/app-release.apk \
|
|
163
|
+
--version 1.0.0-h1 \
|
|
164
|
+
--target-version-code 100
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
To include assets (since 0.1.3), append `--assets`:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
dart run flutter_ota_kit:pack \
|
|
171
|
+
--apk build/app/outputs/flutter-apk/app-release.apk \
|
|
172
|
+
--version 1.0.1 \
|
|
173
|
+
--target-version-code 100 \
|
|
174
|
+
--assets assets/hero.png,assets/strings/zh.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each asset must be registered in the new APK's `pubspec.yaml`. Upload
|
|
178
|
+
`patch.zip` to your backend (or CDN) and have your update endpoint return
|
|
179
|
+
a `PatchInfo` pointing at it.
|
|
180
|
+
|
|
181
|
+
### 4. Apply a patch (most apps just do this)
|
|
182
|
+
|
|
183
|
+
```dart
|
|
184
|
+
final result = await FlutterPatcher.applyPatch(
|
|
185
|
+
PatchInfo(
|
|
186
|
+
version: 'fix-1',
|
|
187
|
+
patchUrl: 'https://your-cdn.com/v100/patch.zip',
|
|
188
|
+
md5: '0123456789abcdef0123456789abcdef',
|
|
189
|
+
targetVersionCode: 100,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (result.ok) {
|
|
194
|
+
// Patch takes effect on the next cold start.
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
If you manage the download yourself, use `applyPatchBytes`:
|
|
199
|
+
|
|
200
|
+
```dart
|
|
201
|
+
final bytes = await loadPatchFromYourSource();
|
|
202
|
+
final result = await FlutterPatcher.applyPatchBytes(
|
|
203
|
+
bytes,
|
|
204
|
+
version: '1.0.0-h1',
|
|
205
|
+
targetVersionCode: 100,
|
|
206
|
+
);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 5. Roll back
|
|
210
|
+
|
|
211
|
+
```dart
|
|
212
|
+
await FlutterPatcher.rollback();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Deletes the current patch. The app reverts to the APK's built-in version on
|
|
216
|
+
the next cold start. This is **not** the same as a server-side rollback —
|
|
217
|
+
this deletes the local patch, it doesn't change what's in your backend.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## How it works
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
┌─────────────────┐
|
|
225
|
+
│ Your backend │ Supabase / Postgres / Cloudflare / AWS / PocketBase / your CDN
|
|
226
|
+
│ (or CDN) │ stores: patch.zip (the diff) + manifest.json (the metadata)
|
|
227
|
+
└────────▲────────┘
|
|
228
|
+
│ 1. check-for-update on launch
|
|
229
|
+
│ 2. server returns PatchInfo (url, md5, signature, version, shouldForceUpdate)
|
|
230
|
+
│
|
|
231
|
+
┌────────┴────────┐
|
|
232
|
+
│ The app │
|
|
233
|
+
│ │ 3. download patch.zip
|
|
234
|
+
│ │ 4. verify MD5 + Ed25519 signature
|
|
235
|
+
│ │ 5. write to local patch dir (atomic rename)
|
|
236
|
+
│ │ 6. cold-restart (forced) OR next cold start (staged)
|
|
237
|
+
│ │ 7. loader hook reads patched libapp.so + asset overlays
|
|
238
|
+
│ │ 8. boot succeeds → keep patch
|
|
239
|
+
│ │ boot fails → auto-rollback + blacklist
|
|
240
|
+
└─────────────────┘
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
A patch is a **byte-for-byte diff** of the new APK's `libapp.so` against the
|
|
244
|
+
old one (the versionCode the user has installed). Asset patches overlay
|
|
245
|
+
`flutter_assets/` files by path. The patch never ships inside the running
|
|
246
|
+
process — it loads on the next cold start. The cold-restart for forced
|
|
247
|
+
updates goes through `restart_app` (with `RestartMode.process`, which
|
|
248
|
+
actually exits the process so the new code reloads).
|
|
249
|
+
|
|
250
|
+
Full internals: [Architecture](doc/architecture.md).
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## What can and can't be patched
|
|
255
|
+
|
|
256
|
+
| ✅ Hot-patchable | ❌ Not hot-patchable |
|
|
257
|
+
|-----------------------------------------------------------------|-----------------------------------------------------------------------|
|
|
258
|
+
| Anything in `lib/` — widgets, logic, routes, constants, tests | Native code (Kotlin / Java / C++ in `android/src/main/`) |
|
|
259
|
+
| Pure-Dart package upgrades (same native side) | `AndroidManifest.xml` changes |
|
|
260
|
+
| Flutter asset files (registered in `pubspec.yaml`) | APK `res/` (icons, layouts, strings.xml) |
|
|
261
|
+
| New `Image.asset()` / `rootBundle.load()` calls pick up new bytes | Flutter Engine upgrades |
|
|
262
|
+
| | Adding or removing native plugins |
|
|
263
|
+
| | Removing assets that exist in the base APK |
|
|
264
|
+
| | `pubspec.yaml` font registration changes |
|
|
265
|
+
|
|
266
|
+
For edge cases (ProGuard/R8, multi-ABI / flavors, state migrations, asset
|
|
267
|
+
overlay quirks), see [API Reference → What can be patched](doc/api-reference.md#what-can-and-cannot-be-patched).
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Safety
|
|
272
|
+
|
|
273
|
+
### Crash protection (on by default)
|
|
274
|
+
|
|
275
|
+
The plugin is **fail-fast**: if a patch causes a boot failure, it auto-rolls
|
|
276
|
+
back and blacklists the offending version so it won't be retried. Tunable
|
|
277
|
+
via `maxCrashCount` (default 1) and `verifyAfter` (default 5s).
|
|
278
|
+
|
|
279
|
+
Design and Android version differences:
|
|
280
|
+
[Crash Protection](doc/crash-protection.md).
|
|
281
|
+
|
|
282
|
+
### Integrity & signing
|
|
283
|
+
|
|
284
|
+
- **MD5** verification is strongly recommended; omit only for quick testing.
|
|
285
|
+
- **Ed25519 signature** verification is available on Android 13+ (API 33).
|
|
286
|
+
Falls back to MD5-only on older Android (with a warning).
|
|
287
|
+
- Patches are bound to the host APK's `versionCode` — old patches expire
|
|
288
|
+
after an APK upgrade and the server stops serving them.
|
|
289
|
+
- Always download over HTTPS. Keep private signing keys on the server only,
|
|
290
|
+
never in the app bundle.
|
|
291
|
+
|
|
292
|
+
### Production tips
|
|
293
|
+
|
|
294
|
+
- **Stage your rollout** (1% → 5% → 20% → 50% → 100%) and monitor crash
|
|
295
|
+
rate at each stage. The SDK records `lastBootDiagnostic` per device.
|
|
296
|
+
- **Report diagnostics** to your analytics pipeline so you can spot a bad
|
|
297
|
+
patch before users hit the app.
|
|
298
|
+
- **Prepare for emergency rollback** — stop returning the bad patch from
|
|
299
|
+
your endpoint; devices that already tripped crash protection have
|
|
300
|
+
rolled back locally and won't re-download.
|
|
301
|
+
|
|
302
|
+
Full release workflow + diagnostic-reporting code:
|
|
303
|
+
[Production Playbook](doc/production-playbook.md).
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## FAQ
|
|
308
|
+
|
|
309
|
+
**Q: Must the patch and base APK use the same Flutter version?**
|
|
310
|
+
A: Yes. `libapp.so` is tightly coupled to the Flutter Engine ABI.
|
|
311
|
+
After upgrading the SDK, ship a new release.
|
|
312
|
+
|
|
313
|
+
**Q: Why doesn't a patch take effect immediately?**
|
|
314
|
+
A: Once `libapp.so` is loaded by the current process, it can't be swapped
|
|
315
|
+
at runtime. The patch is written to disk and loaded on the next cold
|
|
316
|
+
start. Forced updates trigger a process restart so the new code loads
|
|
317
|
+
immediately.
|
|
318
|
+
|
|
319
|
+
**Q: Why does each patch need a `targetVersionCode`?**
|
|
320
|
+
A: Two reasons: (1) old patches expire after an APK upgrade (so a user on
|
|
321
|
+
v2 doesn't accidentally load a v1-targeted patch), and (2) the server
|
|
322
|
+
won't ship a patch built for v1 to a user on v2 (the SDK filters them
|
|
323
|
+
out at update-check time).
|
|
324
|
+
|
|
325
|
+
**Q: How does the client report its app version, and why must it match `--target-app-version`?**
|
|
326
|
+
A: The client reports an `appVersion` that the backend matches against
|
|
327
|
+
each bundle's `target_app_version`. By default this is **auto-detected at
|
|
328
|
+
runtime** from the host app's `versionName` via `package_info_plus` — no
|
|
329
|
+
build flag needed. You can override it explicitly with
|
|
330
|
+
`--dart-define=APP_VERSION=1.2.3` or `SupabaseUpdateConfig.appVersion`.
|
|
331
|
+
The backend keeps only bundles whose `target_app_version` is
|
|
332
|
+
semver-compatible with the reported version. **If they don't match, the
|
|
333
|
+
backend returns no bundle and the app silently stays "up to date"** — so
|
|
334
|
+
always deploy with `--target-app-version` equal to the app's real
|
|
335
|
+
`versionName` (e.g. `1.0.1` for a `version: 1.0.1+2` pubspec). A mismatched
|
|
336
|
+
version is the most common cause of "the update never arrives".
|
|
337
|
+
|
|
338
|
+
More questions: [Full FAQ](doc/faq.md).
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Documentation
|
|
343
|
+
|
|
344
|
+
**Guides** (narrative, in reading order)
|
|
345
|
+
|
|
346
|
+
- [Beginner Guide](doc/beginner-guide.md) — zero-to-first-OTA walkthrough, as a human would do it
|
|
347
|
+
- [Getting Started](doc/getting-started.md) — scaffold → build → deploy in 5 minutes
|
|
348
|
+
- [Developer Guide](doc/developer-guide.md) — full workflow reference (init, migrate, build, deploy, SDK API, targeting, troubleshooting)
|
|
349
|
+
- [Configuration](doc/configuration.md) — every env var, `.env`, resolution order, secrets policy
|
|
350
|
+
- [Backends](doc/backends.md) — Supabase / Postgres / Cloudflare / AWS / PocketBase setup, env vars, CLI snippets
|
|
351
|
+
- [Production Playbook](doc/production-playbook.md) — staged rollout, diagnostics, emergency rollback
|
|
352
|
+
|
|
353
|
+
**Reference**
|
|
354
|
+
|
|
355
|
+
- [API Reference](doc/api-reference.md) — `FlutterPatcher` methods, error codes, asset patching
|
|
356
|
+
- [Architecture](doc/architecture.md) — internals, server protocol, signing, advanced config
|
|
357
|
+
- [CLI Reference](doc/cli-reference.md) — every command, subcommand, and flag
|
|
358
|
+
- [Crash Protection](doc/crash-protection.md) — auto-rollback, blacklist, Android version differences
|
|
359
|
+
- [Golden Testing](doc/golden-testing.md) — how Flutter's pixel-perfect test system works (and how this project uses it)
|
|
360
|
+
|
|
361
|
+
**Other**
|
|
362
|
+
|
|
363
|
+
- [FAQ](doc/faq.md) — versioning, cold start, store policy
|
|
364
|
+
- [CHANGELOG.md](CHANGELOG.md) — release notes
|
|
365
|
+
|
|
366
|
+
中文文档: [README-zh.md](README-zh.md) · [api-reference-zh](doc/api-reference-zh.md) ·
|
|
367
|
+
[architecture-zh](doc/architecture-zh.md) · [crash-protection-zh](doc/crash-protection-zh.md) ·
|
|
368
|
+
[getting-started-zh](doc/getting-started-zh.md) · [production-playbook-zh](doc/production-playbook-zh.md) ·
|
|
369
|
+
[faq-zh](doc/faq-zh.md)
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Who's using it?
|
|
374
|
+
|
|
375
|
+
If you run flutter_ota_kit in production, [open an issue](https://github.com/HYPER12755/flutter-ota-kit/issues) and tell us about your use case — we'd love to list you here.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Contributing
|
|
380
|
+
|
|
381
|
+
Issues and PRs are welcome.
|
|
382
|
+
|
|
383
|
+
Before submitting, please make sure:
|
|
384
|
+
|
|
385
|
+
- `flutter analyze` reports no warnings
|
|
386
|
+
- `flutter test` is fully green
|
|
387
|
+
- If you touched native code, you've run a real-device end-to-end patch / rollback flow
|
|
388
|
+
- If you added a new public API, you've documented it on `dartdoc_options.yaml` topics (the doc appears on pub.dev)
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Root analysis options for the flutter_ota_kit monorepo.
|
|
2
|
+
#
|
|
3
|
+
# The repo is a monorepo: `packages/*`, `plugins/*`, and `example/` are each
|
|
4
|
+
# standalone Dart/Flutter packages that are analyzed (and lint-configured)
|
|
5
|
+
# independently. Excluding them here keeps `dart analyze .` focused on the root
|
|
6
|
+
# device-SDK package (`lib/` + `test/`), which is the only thing the root
|
|
7
|
+
# package owns. Each sub-package is analyzed on its own and is already clean.
|
|
8
|
+
analyzer:
|
|
9
|
+
exclude:
|
|
10
|
+
- packages/**
|
|
11
|
+
- plugins/**
|
|
12
|
+
- example/**
|
|
13
|
+
- node_modules/**
|
|
14
|
+
- npm/**
|
|
15
|
+
- build/**
|
|
16
|
+
- android/**
|
|
17
|
+
- ios/**
|
|
18
|
+
- web/**
|
|
19
|
+
- windows/**
|
|
20
|
+
- macos/**
|
|
21
|
+
- linux/**
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
group = "com.flutter_patcher.flutter_patcher"
|
|
2
|
+
version = "1.0-SNAPSHOT"
|
|
3
|
+
|
|
4
|
+
buildscript {
|
|
5
|
+
ext.kotlin_version = "2.2.20"
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath("com.android.tools.build:gradle:8.11.1")
|
|
13
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
allprojects {
|
|
18
|
+
repositories {
|
|
19
|
+
google()
|
|
20
|
+
mavenCentral()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
apply plugin: "com.android.library"
|
|
25
|
+
|
|
26
|
+
// AGP 9 compiles Kotlin itself ("built-in Kotlin") and rejects the
|
|
27
|
+
// kotlin-android plugin. AGP 9 hosts that opt out via
|
|
28
|
+
// `android.builtInKotlin=false` -- which is what the Flutter 3.44 template
|
|
29
|
+
// generates -- still need us to bring our own KGP, exactly like AGP 8 hosts.
|
|
30
|
+
// So the switch is built-in Kotlin, not the AGP major on its own.
|
|
31
|
+
def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int
|
|
32
|
+
def builtInKotlinFlag = project.findProperty("android.builtInKotlin")
|
|
33
|
+
def builtInKotlin = agpMajor >= 9 &&
|
|
34
|
+
(builtInKotlinFlag == null || Boolean.parseBoolean(builtInKotlinFlag.toString()))
|
|
35
|
+
|
|
36
|
+
// Keep this as a literal `apply plugin:` at line start even though it is
|
|
37
|
+
// guarded. The Flutter Gradle Plugin regex-matches this file's text to decide
|
|
38
|
+
// whether a subproject brings its own KGP; when it finds none it applies
|
|
39
|
+
// kotlin-android to us itself, which hard-fails under built-in Kotlin. The cost
|
|
40
|
+
// is that FGP prints its "plugin applies KGP" warning on AGP 9 even when this
|
|
41
|
+
// branch never runs.
|
|
42
|
+
if (!builtInKotlin) {
|
|
43
|
+
apply plugin: "kotlin-android"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
|
47
|
+
// Kotlin incremental cache path relativization can fail on Windows when a
|
|
48
|
+
// Flutter plugin is compiled from Pub Cache on C: into an app on another
|
|
49
|
+
// drive. Keep this library compile non-incremental; app modules are
|
|
50
|
+
// unaffected.
|
|
51
|
+
incremental = false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
android {
|
|
55
|
+
namespace = "com.flutter_patcher.flutter_patcher"
|
|
56
|
+
|
|
57
|
+
compileSdk = 36
|
|
58
|
+
ndkVersion = "27.0.12077973"
|
|
59
|
+
|
|
60
|
+
compileOptions {
|
|
61
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
62
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!builtInKotlin) {
|
|
66
|
+
// Contributed by KGP, not by AGP -- only exists where KGP was applied.
|
|
67
|
+
kotlinOptions {
|
|
68
|
+
jvmTarget = JavaVersion.VERSION_17
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
sourceSets {
|
|
73
|
+
main.java.srcDirs += "src/main/kotlin"
|
|
74
|
+
test.java.srcDirs += "src/test/kotlin"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
defaultConfig {
|
|
78
|
+
minSdk = 24
|
|
79
|
+
|
|
80
|
+
// ABI 白名单:覆盖主流 Android 设备。Flutter 默认只出 arm64-v8a,
|
|
81
|
+
// 但插件要兼容 32 位旧机型和 x86_64 模拟器。
|
|
82
|
+
ndk {
|
|
83
|
+
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 让 AGP 把本插件的 consumer R8 规则合并到宿主 release build
|
|
87
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
dependencies {
|
|
91
|
+
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
|
92
|
+
testImplementation("org.json:json:20240303")
|
|
93
|
+
testImplementation("org.mockito:mockito-core:5.0.0")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
testOptions {
|
|
97
|
+
unitTests.all {
|
|
98
|
+
useJUnitPlatform()
|
|
99
|
+
jvmArgs "-Dnet.bytebuddy.experimental=true"
|
|
100
|
+
|
|
101
|
+
testLogging {
|
|
102
|
+
events "passed", "skipped", "failed", "standardOut", "standardError"
|
|
103
|
+
outputs.upToDateWhen {false}
|
|
104
|
+
showStandardStreams = true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (builtInKotlin) {
|
|
111
|
+
kotlin {
|
|
112
|
+
compilerOptions {
|
|
113
|
+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# flutter_patcher consumer ProGuard / R8 rules.
|
|
2
|
+
# AGP merges this file into the host app release R8 configuration.
|
|
3
|
+
# Goal: keep the small set of Android / Flutter classes referenced by manifest
|
|
4
|
+
# entries or startup reflection.
|
|
5
|
+
|
|
6
|
+
# Application: referenced by AndroidManifest android:name.
|
|
7
|
+
-keep class com.flutter_patcher.flutter_patcher.FlutterPatcherApplication { *; }
|
|
8
|
+
|
|
9
|
+
# Auto-init ContentProvider: declared by the plugin manifest.
|
|
10
|
+
-keep class com.flutter_patcher.flutter_patcher.FlutterPatcherAutoInitProvider { *; }
|
|
11
|
+
|
|
12
|
+
# Flutter plugin: registered by GeneratedPluginRegistrant.
|
|
13
|
+
-keep class com.flutter_patcher.flutter_patcher.FlutterPatcherPlugin { *; }
|
|
14
|
+
|
|
15
|
+
# PatchedFlutterLoader is installed reflectively and overrides engine init.
|
|
16
|
+
-keep class com.flutter_patcher.flutter_patcher.PatchedFlutterLoader {
|
|
17
|
+
<init>(...);
|
|
18
|
+
public void ensureInitializationComplete(android.content.Context, java.lang.String[]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Flutter Engine reflection targets used by LoaderHook.
|
|
22
|
+
-keep class io.flutter.FlutterInjector { *; }
|
|
23
|
+
-keep class io.flutter.embedding.engine.loader.FlutterLoader { *; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootProject.name = 'flutter_patcher'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
package="com.flutter_patcher.flutter_patcher">
|
|
3
|
+
<!-- 宿主 App 必须自行声明 INTERNET 权限以允许补丁下载 / checkUpdate 发起 HTTP 请求 -->
|
|
4
|
+
|
|
5
|
+
<application>
|
|
6
|
+
<!--
|
|
7
|
+
自动初始化:在 ContentProvider.onCreate 阶段(早于 Application.onCreate /
|
|
8
|
+
任何 FlutterActivity.onCreate)完成熔断检查 + FlutterLoader 反射替换,宿主
|
|
9
|
+
无需接入 Application。
|
|
10
|
+
|
|
11
|
+
需要关掉的极少数场景(如在 Application.attachBaseContext 预热 FlutterEngine)
|
|
12
|
+
可在宿主 Manifest 里用 tools:node="remove" 移除本 provider,然后显式调
|
|
13
|
+
FlutterPatcherApplication.attachPatcher(base)。详见 README。
|
|
14
|
+
-->
|
|
15
|
+
<provider
|
|
16
|
+
android:name="com.flutter_patcher.flutter_patcher.FlutterPatcherAutoInitProvider"
|
|
17
|
+
android:authorities="${applicationId}.flutter_patcher.autoinit"
|
|
18
|
+
android:exported="false"
|
|
19
|
+
android:initOrder="100" />
|
|
20
|
+
</application>
|
|
21
|
+
</manifest>
|
package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* [PatchManager.applyPatch] 的结构化返回值。
|
|
5
|
+
*
|
|
6
|
+
* 通过 MethodChannel 以 `Map<String, Any?>` 形式序列化到 Dart 侧,由
|
|
7
|
+
* `PatchApplyResult.fromNative()` 反解。
|
|
8
|
+
*
|
|
9
|
+
* 错误码与使用建议见 [ApplyErrorCode]。
|
|
10
|
+
*/
|
|
11
|
+
internal data class ApplyResult(
|
|
12
|
+
val ok: Boolean,
|
|
13
|
+
val errorCode: String? = null,
|
|
14
|
+
val message: String? = null
|
|
15
|
+
) {
|
|
16
|
+
fun toMap(): Map<String, Any?> = mapOf(
|
|
17
|
+
"ok" to ok,
|
|
18
|
+
"error" to errorCode,
|
|
19
|
+
"message" to message
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
val SUCCESS = ApplyResult(ok = true)
|
|
24
|
+
|
|
25
|
+
fun failure(code: String, message: String? = null): ApplyResult =
|
|
26
|
+
ApplyResult(ok = false, errorCode = code, message = message)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* applyPatch 可能的失败原因。调用方(Dart 业务层)按需要做不同处理:
|
|
32
|
+
*
|
|
33
|
+
* - [INVALID_ARGS]:服务端下发的 JSON 缺字段 → 告警服务端
|
|
34
|
+
* - [BLACKLISTED]:(version, md5) 已在本地黑名单 → 服务端告警,下架补丁
|
|
35
|
+
* - [NETWORK]:下载失败(重试后依然失败)→ 稍后重试
|
|
36
|
+
* - [MD5_MISMATCH]:下载文件 md5 不匹配 → CDN 脏数据或服务端 md5 算错,检查后重试
|
|
37
|
+
* - [SIGNATURE_INVALID]:Ed25519 验签失败(或 strict 模式下 API < 33 直接拒绝)→
|
|
38
|
+
* 可能被篡改,**不建议**自动重试
|
|
39
|
+
* - [IO_ERROR]:磁盘 / 文件系统错误(磁盘满、权限)→ 稍后重试
|
|
40
|
+
* - [UNKNOWN]:未被分类的异常 → 上报到监控,看日志定位
|
|
41
|
+
*/
|
|
42
|
+
internal object ApplyErrorCode {
|
|
43
|
+
const val INVALID_ARGS = "INVALID_ARGS"
|
|
44
|
+
const val BLACKLISTED = "BLACKLISTED"
|
|
45
|
+
const val NETWORK = "NETWORK"
|
|
46
|
+
const val MD5_MISMATCH = "MD5_MISMATCH"
|
|
47
|
+
const val SIGNATURE_INVALID = "SIGNATURE_INVALID"
|
|
48
|
+
const val UNSUPPORTED_ABI = "UNSUPPORTED_ABI"
|
|
49
|
+
const val ASSET_PACKAGE_INVALID = "ASSET_PACKAGE_INVALID"
|
|
50
|
+
const val IO_ERROR = "IO_ERROR"
|
|
51
|
+
const val UNKNOWN = "UNKNOWN"
|
|
52
|
+
}
|