@_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,201 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import io.flutter.app.FlutterApplication
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 宿主 App 需在 AndroidManifest.xml 中使用这个 Application(或在自己的 Application
|
|
9
|
+
* 里调 [attachPatcher])。
|
|
10
|
+
*
|
|
11
|
+
* 启动流程:
|
|
12
|
+
* 1. attachBaseContext
|
|
13
|
+
* → CrashGuard.shouldLoadPatch() 熔断检查(API 30+ 走 ApplicationExitInfo
|
|
14
|
+
* 精确识别上次进程是真崩溃还是被用户/系统结束;API < 30 走朴素策略:
|
|
15
|
+
* patch_loading=true 即视为一次崩溃)
|
|
16
|
+
* → PatchManager.getValidPatchPath() 磁盘补丁验签
|
|
17
|
+
* → LoaderHook.install() 反射替换 FlutterLoader
|
|
18
|
+
* → CrashGuard.markBooting() 标记「启动中」并记录 pid(API 30+ 用于 ExitInfo 查询)
|
|
19
|
+
* 2. Dart 首帧 → MethodChannel("flutter_ota_kit#reportBootSuccess")
|
|
20
|
+
* → CrashGuard.markBootSuccess() 重置计数
|
|
21
|
+
*
|
|
22
|
+
* API 30+ 上,用户主动从最近任务划掉 / OOM kill / 强停等「非崩溃退出」不计入
|
|
23
|
+
* crash_count,只有真崩溃(CRASH / CRASH_NATIVE / ANR / INITIALIZATION_FAILURE)
|
|
24
|
+
* 才会触发熔断。API < 30 没有这层分类,对长尾设备做 fail-fast 取舍。
|
|
25
|
+
*
|
|
26
|
+
* 用户如果已经有自己的 Application 基类,直接把 [attachPatcher] 搬过去即可,
|
|
27
|
+
* 不需要继承本类。
|
|
28
|
+
*/
|
|
29
|
+
open class FlutterPatcherApplication : FlutterApplication() {
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
private const val TAG = "FlutterPatcher/App"
|
|
33
|
+
|
|
34
|
+
@Volatile
|
|
35
|
+
private var attachAttempted = false
|
|
36
|
+
|
|
37
|
+
@Volatile
|
|
38
|
+
private var attachResult = false
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 在 Application.attachBaseContext(base) 中、super 调用之后调用。
|
|
42
|
+
* 返回补丁是否成功注入。
|
|
43
|
+
*/
|
|
44
|
+
@JvmStatic
|
|
45
|
+
@Synchronized
|
|
46
|
+
fun attachPatcher(context: Context): Boolean {
|
|
47
|
+
if (attachAttempted) {
|
|
48
|
+
Log.d(TAG, "attachPatcher already ran in this process, result=$attachResult")
|
|
49
|
+
return attachResult
|
|
50
|
+
}
|
|
51
|
+
attachAttempted = true
|
|
52
|
+
attachResult = doAttachPatcher(context)
|
|
53
|
+
return attachResult
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private fun doAttachPatcher(context: Context): Boolean {
|
|
57
|
+
// 本次启动是否已往 BootDiagnosticStore 写过精准 status。
|
|
58
|
+
// 用于在收尾阶段判断要不要兜底成 NO_PATCH(避免覆盖更精准的 reason)。
|
|
59
|
+
var diagRecorded = false
|
|
60
|
+
val appVc = PatcherConfig.currentVersionCode(context)
|
|
61
|
+
|
|
62
|
+
return try {
|
|
63
|
+
// 在熔断/校验丢弃前预读 (version, downloadMd5),等会儿入黑名单要用。
|
|
64
|
+
// 即便文件后续被删,这里捕获的字符串仍有效。
|
|
65
|
+
val patchManager = PatchManager(context)
|
|
66
|
+
val crashedMeta = patchManager.currentMeta()
|
|
67
|
+
|
|
68
|
+
val guard = CrashGuard(context)
|
|
69
|
+
val canLoad = guard.shouldLoadPatch { crashCount ->
|
|
70
|
+
// 熔断触发 ⇒ 这个补丁连续没活到 verified,加入黑名单防止重试。
|
|
71
|
+
if (crashedMeta != null) {
|
|
72
|
+
BlacklistStore.add(
|
|
73
|
+
context,
|
|
74
|
+
crashedMeta.first,
|
|
75
|
+
crashedMeta.second,
|
|
76
|
+
BlacklistStore.REASON_BOOT_CRASH,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
BootDiagnosticStore.record(
|
|
80
|
+
context = context,
|
|
81
|
+
status = BootDiagnosticStore.DROPPED_CIRCUIT_BREAKER,
|
|
82
|
+
patchVersion = crashedMeta?.first,
|
|
83
|
+
appVersionCode = appVc,
|
|
84
|
+
crashCount = crashCount,
|
|
85
|
+
message = if (crashedMeta != null)
|
|
86
|
+
"$crashCount boot failure(s); blacklisted (version=${crashedMeta.first})"
|
|
87
|
+
else
|
|
88
|
+
"$crashCount boot failure(s)",
|
|
89
|
+
)
|
|
90
|
+
diagRecorded = true
|
|
91
|
+
}
|
|
92
|
+
if (!canLoad) {
|
|
93
|
+
Log.w(TAG, "circuit breaker tripped, skip patch")
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
val patch = patchManager.getValidPatch { status, version, extras ->
|
|
98
|
+
// md5 / signature 失败 = 强烈"补丁有问题"信号,连带入黑名单。
|
|
99
|
+
// meta_corrupted / version_code_mismatch 不入黑名单(前者 key 不全,后者
|
|
100
|
+
// 属于正常 APK 升级而非补丁本身有问题)。
|
|
101
|
+
val blacklistMd5 = extras["blacklistMd5"] as? String
|
|
102
|
+
val blacklistReason = when (status) {
|
|
103
|
+
BootDiagnosticStore.DROPPED_MD5_MISMATCH ->
|
|
104
|
+
BlacklistStore.REASON_MD5_MISMATCH
|
|
105
|
+
BootDiagnosticStore.DROPPED_SIGNATURE_INVALID ->
|
|
106
|
+
BlacklistStore.REASON_SIGNATURE_INVALID
|
|
107
|
+
else -> null
|
|
108
|
+
}
|
|
109
|
+
if (blacklistReason != null && version != null && !blacklistMd5.isNullOrEmpty()) {
|
|
110
|
+
BlacklistStore.add(context, version, blacklistMd5, blacklistReason)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
BootDiagnosticStore.record(
|
|
114
|
+
context = context,
|
|
115
|
+
status = status,
|
|
116
|
+
patchVersion = version,
|
|
117
|
+
patchTargetVersionCode = extras["patchTargetVersionCode"] as? Long,
|
|
118
|
+
appVersionCode = (extras["appVersionCode"] as? Long) ?: appVc,
|
|
119
|
+
message = extras["message"] as? String,
|
|
120
|
+
)
|
|
121
|
+
diagRecorded = true
|
|
122
|
+
}
|
|
123
|
+
if (patch == null) {
|
|
124
|
+
Log.d(TAG, "no usable patch, boot with built-in libapp.so")
|
|
125
|
+
if (!diagRecorded) {
|
|
126
|
+
// DROPPED_* 是用户最关心的"上次为啥被丢弃"信息,不应被这次
|
|
127
|
+
// 无信息量的 NO_PATCH 覆盖。典型场景:上一次冷启动里 Dart 错误
|
|
128
|
+
// 钩子把补丁判崩 + record(DROPPED_CIRCUIT_BREAKER) + 删补丁,
|
|
129
|
+
// 但进程没死、UI 没建出来;用户划掉重开后这一次进入本分支,
|
|
130
|
+
// 写 NO_PATCH 会让 DiagCard 看不到 trip 信息。
|
|
131
|
+
// 后续装上新补丁时,line 127 会写 PATCHED 自然覆盖,sticky 自动解除。
|
|
132
|
+
val existing = BootDiagnosticStore.read(context)
|
|
133
|
+
val existingStatus = existing?.get("status") as? String
|
|
134
|
+
val shouldPreserve = existingStatus != null &&
|
|
135
|
+
existingStatus.startsWith("DROPPED_")
|
|
136
|
+
if (!shouldPreserve) {
|
|
137
|
+
BootDiagnosticStore.record(
|
|
138
|
+
context = context,
|
|
139
|
+
status = BootDiagnosticStore.NO_PATCH,
|
|
140
|
+
appVersionCode = appVc,
|
|
141
|
+
)
|
|
142
|
+
} else {
|
|
143
|
+
Log.d(TAG, "preserved existing $existingStatus over NO_PATCH")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 走到这里说明补丁已通过校验。预先取版本号,方便后面 record。
|
|
150
|
+
val patchVersion = patchManager.currentVersion().ifEmpty { null }
|
|
151
|
+
|
|
152
|
+
// 标记「启动中」——必须 commit 同步写入,确保进程崩溃前状态已持久化
|
|
153
|
+
guard.markBooting()
|
|
154
|
+
|
|
155
|
+
val attemptedFields = mutableListOf<String>()
|
|
156
|
+
val ok = LoaderHook.install(
|
|
157
|
+
context,
|
|
158
|
+
patch.soPath,
|
|
159
|
+
patch.assetsBundlePath,
|
|
160
|
+
patch.assetsArchivePath,
|
|
161
|
+
attemptedFields
|
|
162
|
+
)
|
|
163
|
+
if (ok) {
|
|
164
|
+
BootDiagnosticStore.record(
|
|
165
|
+
context = context,
|
|
166
|
+
status = BootDiagnosticStore.PATCHED,
|
|
167
|
+
patchVersion = patchVersion,
|
|
168
|
+
appVersionCode = appVc,
|
|
169
|
+
)
|
|
170
|
+
} else {
|
|
171
|
+
// 注入失败:本次启动根本没换 libapp.so,不视为一次崩溃
|
|
172
|
+
guard.reset()
|
|
173
|
+
BootDiagnosticStore.record(
|
|
174
|
+
context = context,
|
|
175
|
+
status = BootDiagnosticStore.HOOK_INSTALL_FAILED,
|
|
176
|
+
patchVersion = patchVersion,
|
|
177
|
+
appVersionCode = appVc,
|
|
178
|
+
attemptedLoaderFields = attemptedFields.toList(),
|
|
179
|
+
message = "FlutterLoader reflection failed; patch retained",
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
ok
|
|
183
|
+
} catch (e: Exception) {
|
|
184
|
+
Log.e(TAG, "attachPatcher failed, fallback to built-in", e)
|
|
185
|
+
CrashGuard(context).reset()
|
|
186
|
+
BootDiagnosticStore.record(
|
|
187
|
+
context = context,
|
|
188
|
+
status = BootDiagnosticStore.UNKNOWN,
|
|
189
|
+
appVersionCode = appVc,
|
|
190
|
+
message = e.message ?: e.javaClass.simpleName,
|
|
191
|
+
)
|
|
192
|
+
false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override fun attachBaseContext(base: Context) {
|
|
198
|
+
super.attachBaseContext(base)
|
|
199
|
+
attachPatcher(this)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import android.content.ContentProvider
|
|
4
|
+
import android.content.ContentValues
|
|
5
|
+
import android.database.Cursor
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.util.Log
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 自动初始化 ContentProvider —— 宿主无需改 Application 即可挂上补丁加载。
|
|
11
|
+
*
|
|
12
|
+
* # 生命周期
|
|
13
|
+
* Android `ActivityThread.handleBindApplication` 的固定顺序:
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* Application.attachBaseContext()
|
|
17
|
+
* ↓
|
|
18
|
+
* installContentProviders() ← 本 provider.onCreate 在这里触发
|
|
19
|
+
* ↓
|
|
20
|
+
* Application.onCreate()
|
|
21
|
+
* ↓
|
|
22
|
+
* Activity lifecycle(FlutterActivity.onCreate → 首次 new FlutterEngine)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* 插件反射替换 `FlutterInjector.flutterLoader` 只要求"早于第一次
|
|
26
|
+
* `FlutterInjector.instance()` 真正被使用",ContentProvider 这个时机足够。
|
|
27
|
+
* 这是 Firebase / WorkManager / androidx.startup 都在用的自动初始化模式。
|
|
28
|
+
*
|
|
29
|
+
* # 边界:什么时候走不通
|
|
30
|
+
* 宿主在自己 `Application.attachBaseContext` 里 **预热 FlutterEngine**(罕见,
|
|
31
|
+
* 主要出现在大厂混合工程的冷启动优化)。此时 provider 还没轮到、Engine 已起来,
|
|
32
|
+
* 反射来不及。这类工程应在宿主 Manifest 里关掉自动初始化,改为显式调用:
|
|
33
|
+
*
|
|
34
|
+
* ```xml
|
|
35
|
+
* <provider
|
|
36
|
+
* android:name="com.flutter_patcher.flutter_patcher.FlutterPatcherAutoInitProvider"
|
|
37
|
+
* android:authorities="${applicationId}.flutter_patcher.autoinit"
|
|
38
|
+
* tools:node="remove" />
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* 然后在自己的 Application 里:
|
|
42
|
+
*
|
|
43
|
+
* ```kotlin
|
|
44
|
+
* override fun attachBaseContext(base: Context) {
|
|
45
|
+
* super.attachBaseContext(base)
|
|
46
|
+
* FlutterPatcherApplication.attachPatcher(base)
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
class FlutterPatcherAutoInitProvider : ContentProvider() {
|
|
51
|
+
|
|
52
|
+
companion object {
|
|
53
|
+
private const val TAG = "FlutterPatcher/AutoInit"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun onCreate(): Boolean {
|
|
57
|
+
val ctx = context ?: run {
|
|
58
|
+
Log.w(TAG, "context is null, skip auto init")
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
Log.d(TAG, "auto init via ContentProvider.onCreate()")
|
|
62
|
+
FlutterPatcherApplication.attachPatcher(ctx)
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ==================== no-op data surface ====================
|
|
67
|
+
// 本 provider 不对外暴露任何数据;所有 CRUD 都返回空 / 0 即可。
|
|
68
|
+
|
|
69
|
+
override fun query(
|
|
70
|
+
uri: Uri,
|
|
71
|
+
projection: Array<out String>?,
|
|
72
|
+
selection: String?,
|
|
73
|
+
selectionArgs: Array<out String>?,
|
|
74
|
+
sortOrder: String?
|
|
75
|
+
): Cursor? = null
|
|
76
|
+
|
|
77
|
+
override fun getType(uri: Uri): String? = null
|
|
78
|
+
|
|
79
|
+
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
|
80
|
+
|
|
81
|
+
override fun delete(
|
|
82
|
+
uri: Uri,
|
|
83
|
+
selection: String?,
|
|
84
|
+
selectionArgs: Array<out String>?
|
|
85
|
+
): Int = 0
|
|
86
|
+
|
|
87
|
+
override fun update(
|
|
88
|
+
uri: Uri,
|
|
89
|
+
values: ContentValues?,
|
|
90
|
+
selection: String?,
|
|
91
|
+
selectionArgs: Array<out String>?
|
|
92
|
+
): Int = 0
|
|
93
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
|
9
|
+
import io.flutter.plugin.common.EventChannel
|
|
10
|
+
import io.flutter.plugin.common.MethodCall
|
|
11
|
+
import io.flutter.plugin.common.MethodChannel
|
|
12
|
+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
|
13
|
+
import io.flutter.plugin.common.MethodChannel.Result
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Dart ↔ Android 的 MethodChannel 处理器。
|
|
17
|
+
*
|
|
18
|
+
* Channel 名:
|
|
19
|
+
* - MethodChannel `flutter_ota_kit` —— RPC(saveConfig / applyPatch / ...)
|
|
20
|
+
* - EventChannel `flutter_ota_kit/events` —— applyPatch 过程的阶段 / 进度事件
|
|
21
|
+
*
|
|
22
|
+
* MethodChannel 方法:
|
|
23
|
+
* - saveConfig(publicKeyBase64, maxCrashCount, strictSignature,
|
|
24
|
+
* loaderFieldCandidates, loaderFallbackHeuristic)
|
|
25
|
+
* - markBooting() — Dart init 最开头调用,补写一次「启动中」
|
|
26
|
+
* - reportBootSuccess() — Dart 首帧渲染时调用,清熔断
|
|
27
|
+
* - reportDartBootError(Map) — 引导阶段 Dart 未捕获异常上报,等同一次真崩溃
|
|
28
|
+
* - applyPatch(Map) -> Map{ok, error, message}
|
|
29
|
+
* - rollback()
|
|
30
|
+
* - currentVersion() -> String?
|
|
31
|
+
* - lastBootDiagnostic() -> Map? — 上次冷启动补丁加载诊断(见 BootDiagnosticStore)
|
|
32
|
+
* - cacheDir() -> String — 插件可写的临时目录,供 Dart applyPatchBytes 内部 staging 使用
|
|
33
|
+
* - appVersionCode() -> Long — 当前宿主 APK versionCode,A3 便捷查询
|
|
34
|
+
* - deviceAbi() -> String — 当前设备首选 ABI,用于服务端按 ABI 分发补丁
|
|
35
|
+
* - blacklist() -> List<Map> — 已知"装上就出事"的补丁本地黑名单
|
|
36
|
+
* - clearBlacklist() — 清空黑名单(仅调试 / 显式恢复用)
|
|
37
|
+
*/
|
|
38
|
+
class FlutterPatcherPlugin :
|
|
39
|
+
FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
|
|
40
|
+
|
|
41
|
+
companion object {
|
|
42
|
+
private const val TAG = "FlutterPatcher/Plugin"
|
|
43
|
+
private const val CHANNEL = "flutter_ota_kit"
|
|
44
|
+
private const val EVENT_CHANNEL = "flutter_ota_kit/events"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private lateinit var channel: MethodChannel
|
|
48
|
+
private lateinit var eventChannel: EventChannel
|
|
49
|
+
private lateinit var appContext: Context
|
|
50
|
+
private val main = Handler(Looper.getMainLooper())
|
|
51
|
+
|
|
52
|
+
@Volatile
|
|
53
|
+
private var eventSink: EventChannel.EventSink? = null
|
|
54
|
+
|
|
55
|
+
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
|
56
|
+
appContext = binding.applicationContext
|
|
57
|
+
channel = MethodChannel(binding.binaryMessenger, CHANNEL)
|
|
58
|
+
channel.setMethodCallHandler(this)
|
|
59
|
+
eventChannel = EventChannel(binding.binaryMessenger, EVENT_CHANNEL)
|
|
60
|
+
eventChannel.setStreamHandler(this)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ==================== EventChannel ====================
|
|
64
|
+
|
|
65
|
+
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
|
66
|
+
eventSink = events
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun onCancel(arguments: Any?) {
|
|
70
|
+
eventSink = null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun emitProgress(phase: String, received: Long, total: Long) {
|
|
74
|
+
val sink = eventSink ?: return
|
|
75
|
+
val event = mapOf(
|
|
76
|
+
"phase" to phase,
|
|
77
|
+
"received" to received,
|
|
78
|
+
"total" to total
|
|
79
|
+
)
|
|
80
|
+
main.post { sink.success(event) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onMethodCall(call: MethodCall, result: Result) {
|
|
84
|
+
when (call.method) {
|
|
85
|
+
"saveConfig" -> handleSaveConfig(call, result)
|
|
86
|
+
"markBooting" -> {
|
|
87
|
+
CrashGuard(appContext).markBooting()
|
|
88
|
+
result.success(null)
|
|
89
|
+
}
|
|
90
|
+
"reportBootSuccess" -> {
|
|
91
|
+
CrashGuard(appContext).markBootSuccess()
|
|
92
|
+
result.success(null)
|
|
93
|
+
}
|
|
94
|
+
"reportDartBootError" -> handleReportDartBootError(call, result)
|
|
95
|
+
"applyPatch" -> handleApplyPatch(call, result)
|
|
96
|
+
"rollback" -> handleRollback(result)
|
|
97
|
+
"rollbackToPrevious" -> handleRollbackToPrevious(result)
|
|
98
|
+
"currentVersion" -> handleCurrentVersion(result)
|
|
99
|
+
"lastBootDiagnostic" -> handleLastBootDiagnostic(result)
|
|
100
|
+
"cacheDir" -> result.success(appContext.cacheDir.absolutePath)
|
|
101
|
+
"appVersionCode" -> result.success(PatcherConfig.currentVersionCode(appContext))
|
|
102
|
+
"deviceAbi" -> result.success(Build.SUPPORTED_ABIS.firstOrNull().orEmpty())
|
|
103
|
+
"blacklist" -> result.success(BlacklistStore.entries(appContext))
|
|
104
|
+
"clearBlacklist" -> {
|
|
105
|
+
BlacklistStore.clear(appContext)
|
|
106
|
+
result.success(null)
|
|
107
|
+
}
|
|
108
|
+
"reportApplyFailure" -> handleReportApplyFailure(call, result)
|
|
109
|
+
"isVersionBlacklisted" -> handleIsVersionBlacklisted(call, result)
|
|
110
|
+
else -> result.notImplemented()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
|
115
|
+
channel.setMethodCallHandler(null)
|
|
116
|
+
eventChannel.setStreamHandler(null)
|
|
117
|
+
eventSink = null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ==================== handlers ====================
|
|
121
|
+
|
|
122
|
+
private fun handleSaveConfig(call: MethodCall, result: Result) {
|
|
123
|
+
val pk = call.argument<String>("publicKeyBase64") ?: ""
|
|
124
|
+
val max = call.argument<Int>("maxCrashCount") ?: PatcherConfig.DEFAULT_MAX_CRASH
|
|
125
|
+
val strict = call.argument<Boolean>("strictSignature") ?: PatcherConfig.DEFAULT_STRICT_SIG
|
|
126
|
+
val fields = call.argument<List<String>>("loaderFieldCandidates") ?: emptyList()
|
|
127
|
+
val heuristic = call.argument<Boolean>("loaderFallbackHeuristic")
|
|
128
|
+
?: PatcherConfig.DEFAULT_LOADER_HEURISTIC
|
|
129
|
+
val maxPatchHistory = call.argument<Int>("maxPatchHistory") ?: PatcherConfig.MAX_PATCH_HISTORY
|
|
130
|
+
val maxAssetHistory = call.argument<Int>("maxAssetHistory") ?: PatcherConfig.MAX_ASSET_HISTORY
|
|
131
|
+
PatcherConfig.saveConfig(appContext, pk, max, strict, fields, heuristic, maxPatchHistory, maxAssetHistory)
|
|
132
|
+
result.success(null)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private fun handleApplyPatch(call: MethodCall, result: Result) {
|
|
136
|
+
@Suppress("UNCHECKED_CAST")
|
|
137
|
+
val args = call.arguments as? Map<String, Any?>
|
|
138
|
+
if (args == null) {
|
|
139
|
+
result.error("INVALID_ARGS", "applyPatch expects a Map", null)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
Thread {
|
|
143
|
+
val progress: ProgressCallback = { phase, received, total ->
|
|
144
|
+
emitProgress(phase, received, total)
|
|
145
|
+
}
|
|
146
|
+
val applyResult = try {
|
|
147
|
+
PatchManager(appContext, progress).applyPatch(args)
|
|
148
|
+
} catch (e: Exception) {
|
|
149
|
+
Log.e(TAG, "applyPatch error", e)
|
|
150
|
+
ApplyResult.failure(ApplyErrorCode.UNKNOWN, e.message ?: e.javaClass.simpleName)
|
|
151
|
+
}
|
|
152
|
+
main.post { result.success(applyResult.toMap()) }
|
|
153
|
+
}.start()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private fun handleRollback(result: Result) {
|
|
157
|
+
Thread {
|
|
158
|
+
try {
|
|
159
|
+
PatchManager(appContext).rollback()
|
|
160
|
+
} catch (e: Exception) {
|
|
161
|
+
Log.e(TAG, "rollback error", e)
|
|
162
|
+
}
|
|
163
|
+
main.post { result.success(null) }
|
|
164
|
+
}.start()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private fun handleRollbackToPrevious(result: Result) {
|
|
168
|
+
Thread {
|
|
169
|
+
try {
|
|
170
|
+
val outcome = PatchManager(appContext).rollbackToPrevious()
|
|
171
|
+
main.post { result.success(outcome.name) }
|
|
172
|
+
} catch (e: Exception) {
|
|
173
|
+
Log.e(TAG, "rollbackToPrevious error", e)
|
|
174
|
+
main.post { result.success(RollbackResult.FALLBACK_TO_BASE.name) }
|
|
175
|
+
}
|
|
176
|
+
}.start()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun handleCurrentVersion(result: Result) {
|
|
180
|
+
val v = PatchManager(appContext).currentVersion()
|
|
181
|
+
result.success(if (v.isEmpty()) null else v)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private fun handleLastBootDiagnostic(result: Result) {
|
|
185
|
+
// null 表示从未 record 过(首次安装 / pm clear 后第一次启动且尚未到首帧)
|
|
186
|
+
result.success(BootDiagnosticStore.read(appContext))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Dart 侧 PlatformDispatcher.onError / FlutterError.onError 在「verifyAfter 窗口内」
|
|
191
|
+
* 抓到的未捕获异常 ⇒ 等同 ApplicationExitInfo.REASON_CRASH 处理:crash_count += 1,
|
|
192
|
+
* 达阈值则 onTrip 回调里走完整的"加黑名单 + 记诊断 + 删补丁"链路。
|
|
193
|
+
*
|
|
194
|
+
* 与 attachPatcher 里 shouldLoadPatch 的 onTrip 逻辑保持一致,让用户在 DiagCard
|
|
195
|
+
* 上看到 status=DROPPED_CIRCUIT_BREAKER 而不是上一次的 status=PATCHED 残影。
|
|
196
|
+
*/
|
|
197
|
+
private fun handleReportDartBootError(call: MethodCall, result: Result) {
|
|
198
|
+
val message = call.argument<String>("message")
|
|
199
|
+
val patchManager = PatchManager(appContext)
|
|
200
|
+
val crashedMeta = patchManager.currentMeta()
|
|
201
|
+
val appVc = PatcherConfig.currentVersionCode(appContext)
|
|
202
|
+
|
|
203
|
+
CrashGuard(appContext).reportDartBootError(message) { crashCount ->
|
|
204
|
+
if (crashedMeta != null) {
|
|
205
|
+
BlacklistStore.add(
|
|
206
|
+
appContext,
|
|
207
|
+
crashedMeta.first,
|
|
208
|
+
crashedMeta.second,
|
|
209
|
+
BlacklistStore.REASON_BOOT_CRASH,
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
BootDiagnosticStore.record(
|
|
213
|
+
context = appContext,
|
|
214
|
+
status = BootDiagnosticStore.DROPPED_CIRCUIT_BREAKER,
|
|
215
|
+
patchVersion = crashedMeta?.first,
|
|
216
|
+
appVersionCode = appVc,
|
|
217
|
+
crashCount = crashCount,
|
|
218
|
+
message = if (crashedMeta != null)
|
|
219
|
+
"$crashCount Dart boot failure(s); blacklisted (version=${crashedMeta.first}); ${message ?: ""}"
|
|
220
|
+
else
|
|
221
|
+
"$crashCount Dart boot failure(s); ${message ?: ""}",
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
result.success(null)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private fun handleReportApplyFailure(call: MethodCall, result: Result) {
|
|
228
|
+
val version = call.argument<String>("version") ?: ""
|
|
229
|
+
val md5 = call.argument<String>("md5") ?: ""
|
|
230
|
+
val reason = call.argument<String>("reason") ?: "APPLY_FAILED"
|
|
231
|
+
BlacklistStore.add(appContext, version, md5, reason)
|
|
232
|
+
result.success(null)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fun handleIsVersionBlacklisted(call: MethodCall, result: Result) {
|
|
236
|
+
val version = call.argument<String>("version") ?: ""
|
|
237
|
+
val md5 = call.argument<String>("md5") ?: ""
|
|
238
|
+
val found = if (md5.isNotEmpty()) {
|
|
239
|
+
BlacklistStore.contains(appContext, version, md5)
|
|
240
|
+
} else {
|
|
241
|
+
BlacklistStore.containsByVersion(appContext, version)
|
|
242
|
+
}
|
|
243
|
+
result.success(found)
|
|
244
|
+
}
|
|
245
|
+
}
|