@_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
package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import org.json.JSONArray
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 热更新运行时配置读写,统一走 SharedPreferences。
|
|
12
|
+
*
|
|
13
|
+
* 所有键值由 Dart 侧在 FlutterPatcher.init() 时下发,**不在** 库里硬编码任何
|
|
14
|
+
* 业务相关默认值(服务端 URL、公钥、渠道等)。
|
|
15
|
+
*/
|
|
16
|
+
internal object PatcherConfig {
|
|
17
|
+
|
|
18
|
+
const val PREFS_NAME = "flutter_patcher_prefs"
|
|
19
|
+
|
|
20
|
+
// ---- Config keys ----
|
|
21
|
+
private const val KEY_PUBLIC_KEY = "public_key_base64"
|
|
22
|
+
private const val KEY_MAX_CRASH = "max_crash_count"
|
|
23
|
+
private const val KEY_STRICT_SIG = "strict_signature"
|
|
24
|
+
private const val KEY_LOADER_FIELDS = "loader_field_candidates"
|
|
25
|
+
private const val KEY_LOADER_HEURISTIC = "loader_fallback_heuristic"
|
|
26
|
+
private const val KEY_MAX_PATCH_HISTORY = "max_patch_history"
|
|
27
|
+
private const val KEY_MAX_ASSET_HISTORY = "max_asset_history"
|
|
28
|
+
|
|
29
|
+
// ---- Runtime state keys (CrashGuard / PatchManager) ----
|
|
30
|
+
const val KEY_CRASH_COUNT = "crash_count"
|
|
31
|
+
const val KEY_PATCH_LOADING = "patch_loading"
|
|
32
|
+
|
|
33
|
+
/** PID written at [com.flutter_patcher.flutter_patcher.CrashGuard.markBooting]; consumed
|
|
34
|
+
* next cold start to look up `ActivityManager.getHistoricalProcessExitReasons` (API 30+).
|
|
35
|
+
* Unused on API < 30 — that path uses the naive "patch_loading=true ⇒ crash" rule. */
|
|
36
|
+
const val KEY_LAST_BOOTING_PID = "last_booting_pid"
|
|
37
|
+
|
|
38
|
+
/** Wall-clock time (epoch millis) when the patched process began booting, written together
|
|
39
|
+
* with [KEY_LAST_BOOTING_PID] at markBooting. Used on API 30+ to bound crash attribution:
|
|
40
|
+
* a crash is only treated as a *patch boot* failure when it occurred within
|
|
41
|
+
* [BOOT_CRASH_WINDOW_MS] of this timestamp. Prevents an unrelated runtime crash/ANR hours
|
|
42
|
+
* into a healthy session from tripping the circuit breaker and reverting the patch. */
|
|
43
|
+
const val KEY_BOOT_STARTED_AT = "boot_started_at"
|
|
44
|
+
|
|
45
|
+
// ---- File layout ----
|
|
46
|
+
const val PATCH_DIR = "flutter_patcher"
|
|
47
|
+
const val PATCH_FILENAME = "libapp_patch.so"
|
|
48
|
+
const val META_FILENAME = "patch_meta.json"
|
|
49
|
+
|
|
50
|
+
// ---- Meta JSON keys ----
|
|
51
|
+
const val META_KEY_TARGET_VERSION_CODE = "targetVersionCode"
|
|
52
|
+
|
|
53
|
+
// ---- Sentinels ----
|
|
54
|
+
const val INVALID_VERSION_CODE = -1L
|
|
55
|
+
|
|
56
|
+
// ---- Patch history limits ----
|
|
57
|
+
const val MAX_PATCH_HISTORY = 4
|
|
58
|
+
const val MAX_ASSET_HISTORY = 4
|
|
59
|
+
|
|
60
|
+
// ---- Defaults ----
|
|
61
|
+
/**
|
|
62
|
+
* 默认 1:补丁连续启动失败 1 次即立刻丢弃 + 入黑名单(fail-fast)。
|
|
63
|
+
*
|
|
64
|
+
* 设计依据:补丁加载后崩溃是明确"补丁有问题"信号,不应该再赌一次让用户多崩
|
|
65
|
+
* 一次。0.1 之前默认是 2,行为是"崩 2 次才回滚",已确认对用户体验更糟。
|
|
66
|
+
* 业务侧若想保留旧行为,显式传 `FlutterPatcher.init(maxCrashCount: 2)`。
|
|
67
|
+
*/
|
|
68
|
+
const val DEFAULT_MAX_CRASH = 1
|
|
69
|
+
const val DEFAULT_STRICT_SIG = true
|
|
70
|
+
const val DEFAULT_LOADER_HEURISTIC = false
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* How long after boot start a process death still counts as a *patch boot*
|
|
74
|
+
* failure (API 30+ ExitInfo path). Deaths later than this are treated as
|
|
75
|
+
* ordinary runtime exits and NOT charged against the patch.
|
|
76
|
+
*
|
|
77
|
+
* Rationale: once the app has rendered its first frame and run for a while,
|
|
78
|
+
* a later crash/ANR/force-stop is not evidence that the patch is bad. Before
|
|
79
|
+
* this bound, any REASON_CRASH / REASON_ANR / REASON_CRASH_NATIVE recorded
|
|
80
|
+
* for the last booting pid — even hours into a healthy session — was counted,
|
|
81
|
+
* so the circuit breaker would delete the patch and the app would silently
|
|
82
|
+
* revert to the pre-OTA build. 30s comfortably covers real boot-time crashes
|
|
83
|
+
* while excluding steady-state failures.
|
|
84
|
+
*/
|
|
85
|
+
const val BOOT_CRASH_WINDOW_MS = 30_000L
|
|
86
|
+
|
|
87
|
+
fun prefs(context: Context): SharedPreferences =
|
|
88
|
+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
89
|
+
|
|
90
|
+
internal fun encodeLoaderFieldCandidates(fields: List<String>): String {
|
|
91
|
+
val normalized = fields
|
|
92
|
+
.map { it.trim() }
|
|
93
|
+
.filter { it.isNotEmpty() }
|
|
94
|
+
.distinct()
|
|
95
|
+
return JSONArray(normalized).toString()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
internal fun decodeLoaderFieldCandidates(raw: Any?): List<String> {
|
|
99
|
+
return when (raw) {
|
|
100
|
+
is String -> try {
|
|
101
|
+
val arr = JSONArray(raw)
|
|
102
|
+
List(arr.length()) { arr.optString(it) }
|
|
103
|
+
.map { it.trim() }
|
|
104
|
+
.filter { it.isNotEmpty() }
|
|
105
|
+
.distinct()
|
|
106
|
+
} catch (_: Exception) {
|
|
107
|
+
emptyList()
|
|
108
|
+
}
|
|
109
|
+
else -> emptyList()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun saveConfig(
|
|
114
|
+
context: Context,
|
|
115
|
+
publicKeyBase64: String,
|
|
116
|
+
maxCrashCount: Int,
|
|
117
|
+
strictSignature: Boolean,
|
|
118
|
+
loaderFieldCandidates: List<String>,
|
|
119
|
+
loaderFallbackHeuristic: Boolean,
|
|
120
|
+
maxPatchHistory: Int = MAX_PATCH_HISTORY,
|
|
121
|
+
maxAssetHistory: Int = MAX_ASSET_HISTORY
|
|
122
|
+
) {
|
|
123
|
+
prefs(context).edit()
|
|
124
|
+
.putString(KEY_PUBLIC_KEY, publicKeyBase64)
|
|
125
|
+
.putInt(KEY_MAX_CRASH, maxCrashCount.coerceAtLeast(1))
|
|
126
|
+
.putBoolean(KEY_STRICT_SIG, strictSignature)
|
|
127
|
+
.putString(KEY_LOADER_FIELDS, encodeLoaderFieldCandidates(loaderFieldCandidates))
|
|
128
|
+
.putBoolean(KEY_LOADER_HEURISTIC, loaderFallbackHeuristic)
|
|
129
|
+
.putInt(KEY_MAX_PATCH_HISTORY, maxPatchHistory.coerceAtLeast(0))
|
|
130
|
+
.putInt(KEY_MAX_ASSET_HISTORY, maxAssetHistory.coerceAtLeast(0))
|
|
131
|
+
.apply()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fun publicKey(context: Context): String =
|
|
135
|
+
prefs(context).getString(KEY_PUBLIC_KEY, "") ?: ""
|
|
136
|
+
|
|
137
|
+
fun maxCrashCount(context: Context): Int =
|
|
138
|
+
prefs(context).getInt(KEY_MAX_CRASH, DEFAULT_MAX_CRASH)
|
|
139
|
+
|
|
140
|
+
fun strictSignature(context: Context): Boolean =
|
|
141
|
+
prefs(context).getBoolean(KEY_STRICT_SIG, DEFAULT_STRICT_SIG)
|
|
142
|
+
|
|
143
|
+
fun loaderFieldCandidates(context: Context): List<String> =
|
|
144
|
+
decodeLoaderFieldCandidates(prefs(context).all[KEY_LOADER_FIELDS])
|
|
145
|
+
|
|
146
|
+
fun loaderFallbackHeuristic(context: Context): Boolean =
|
|
147
|
+
prefs(context).getBoolean(KEY_LOADER_HEURISTIC, DEFAULT_LOADER_HEURISTIC)
|
|
148
|
+
|
|
149
|
+
fun maxPatchHistory(context: Context): Int =
|
|
150
|
+
prefs(context).getInt(KEY_MAX_PATCH_HISTORY, MAX_PATCH_HISTORY)
|
|
151
|
+
|
|
152
|
+
fun maxAssetHistory(context: Context): Int =
|
|
153
|
+
prefs(context).getInt(KEY_MAX_ASSET_HISTORY, MAX_ASSET_HISTORY)
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 读取当前宿主 APK 的 versionCode(API 28+ 用 longVersionCode,以下降级)。
|
|
157
|
+
* 查询自身包名不应失败,失败时返回 [INVALID_VERSION_CODE] 作为兜底。
|
|
158
|
+
*/
|
|
159
|
+
fun currentVersionCode(context: Context): Long {
|
|
160
|
+
return try {
|
|
161
|
+
val pi = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
162
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
163
|
+
pi.longVersionCode
|
|
164
|
+
} else {
|
|
165
|
+
@Suppress("DEPRECATION")
|
|
166
|
+
pi.versionCode.toLong()
|
|
167
|
+
}
|
|
168
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
169
|
+
Log.e("FlutterPatcher/Config", "own package not found", e)
|
|
170
|
+
INVALID_VERSION_CODE
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import android.os.Build
|
|
4
|
+
import android.util.Base64
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import java.io.File
|
|
7
|
+
import java.security.KeyFactory
|
|
8
|
+
import java.security.MessageDigest
|
|
9
|
+
import java.security.Signature
|
|
10
|
+
import java.security.spec.X509EncodedKeySpec
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 补丁文件完整性与签名校验。
|
|
14
|
+
*
|
|
15
|
+
* 策略:
|
|
16
|
+
* - MD5 校验:始终执行(来自 [com.flutter_patcher.flutter_patcher.PatchInfo.md5])
|
|
17
|
+
* - Ed25519 签名:
|
|
18
|
+
* - signature 为空 → 跳过(仅靠 MD5 + 传输层防篡改)
|
|
19
|
+
* - signature 非空 且 API >= 33 → 使用 JDK 原生 Ed25519 验签
|
|
20
|
+
* - signature 非空 且 API < 33:
|
|
21
|
+
* - strictSignature=true(默认安全) → **拒绝加载**,防止攻击者通过降级到低
|
|
22
|
+
* 版本设备绕过签名校验
|
|
23
|
+
* - strictSignature=false → 跳过验签(调用方显式选择降级)
|
|
24
|
+
*
|
|
25
|
+
* 签名的消息体约定为 **MD5 小写 hex 字符串的 UTF-8 字节**,
|
|
26
|
+
* 与 Dart 侧 PatchInfo.signature 含义保持一致。
|
|
27
|
+
*/
|
|
28
|
+
internal object SignatureVerifier {
|
|
29
|
+
|
|
30
|
+
private const val TAG = "FlutterPatcher/Sig"
|
|
31
|
+
|
|
32
|
+
/** 计算文件 MD5(小写 hex)。 */
|
|
33
|
+
fun md5(file: File): String {
|
|
34
|
+
val digest = MessageDigest.getInstance("MD5")
|
|
35
|
+
file.inputStream().use { input ->
|
|
36
|
+
val buf = ByteArray(8192)
|
|
37
|
+
while (true) {
|
|
38
|
+
val n = input.read(buf)
|
|
39
|
+
if (n <= 0) break
|
|
40
|
+
digest.update(buf, 0, n)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return digest.digest().joinToString("") { "%02x".format(it) }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 仅校验 Ed25519 签名(调用方已确认 MD5 匹配)。
|
|
48
|
+
*
|
|
49
|
+
* - signature 为空 → 跳过返回 true
|
|
50
|
+
* - signature 非空但 publicKey 未配置 → 拒绝
|
|
51
|
+
* - API < 33 且 strictSignature=true → 拒绝(防止降级攻击)
|
|
52
|
+
* - API < 33 且 strictSignature=false → 跳过返回 true
|
|
53
|
+
* - API >= 33 → 真实 Ed25519 验签
|
|
54
|
+
*
|
|
55
|
+
* @param md5HexLower 已匹配并**小写化**的 md5 hex 字符串(作为签名消息体)
|
|
56
|
+
*/
|
|
57
|
+
fun verifySignatureOnly(
|
|
58
|
+
md5HexLower: String,
|
|
59
|
+
signatureBase64: String,
|
|
60
|
+
publicKeyBase64: String,
|
|
61
|
+
strictSignature: Boolean = true
|
|
62
|
+
): Boolean {
|
|
63
|
+
val sig = signatureBase64.trim()
|
|
64
|
+
if (sig.isEmpty() || sig.equals("null", ignoreCase = true)) {
|
|
65
|
+
Log.d(TAG, "signature empty, skip Ed25519 check")
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
if (publicKeyBase64.isEmpty()) {
|
|
69
|
+
Log.w(TAG, "signature present but no public key configured, reject")
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Build.VERSION.SDK_INT < 33) {
|
|
74
|
+
if (strictSignature) {
|
|
75
|
+
Log.e(
|
|
76
|
+
TAG,
|
|
77
|
+
"API ${Build.VERSION.SDK_INT} < 33, Ed25519 not supported; strict mode rejects. " +
|
|
78
|
+
"Pass strictSignature=false to FlutterPatcher.init to downgrade (NOT recommended)."
|
|
79
|
+
)
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
Log.w(
|
|
83
|
+
TAG,
|
|
84
|
+
"API ${Build.VERSION.SDK_INT} < 33, Ed25519 not supported; strictSignature=false, skip"
|
|
85
|
+
)
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return try {
|
|
90
|
+
verifyEd25519(md5HexLower, sig, publicKeyBase64)
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
Log.e(TAG, "Ed25519 verify failed", e)
|
|
93
|
+
false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* [verify] 的细粒度结果。调用方据此可区分"被丢弃的具体原因"用于诊断上报,
|
|
99
|
+
* 见 [BootDiagnosticStore.DROPPED_MD5_MISMATCH] / [BootDiagnosticStore.DROPPED_SIGNATURE_INVALID]。
|
|
100
|
+
*/
|
|
101
|
+
enum class VerifyResult { OK, MD5_MISMATCH, SIGNATURE_INVALID }
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 完整校验(细粒度版本):MD5 + 可选 Ed25519。返回失败原因分类。
|
|
105
|
+
*
|
|
106
|
+
* 注意:[expectedMd5] 为空时整体跳过 MD5 与签名校验,直接返回
|
|
107
|
+
* [VerifyResult.OK](调用方明确选择"不下发 md5",仅依赖 HTTPS)。
|
|
108
|
+
* 启动路径目前仍依赖 meta.effectiveMd5 非空,故启动期不会走到这条分支;
|
|
109
|
+
* 该分支主要服务于 [PatchManager.applyPatch] 期间的重复使用。
|
|
110
|
+
*/
|
|
111
|
+
fun verifyDetailed(
|
|
112
|
+
file: File,
|
|
113
|
+
expectedMd5: String,
|
|
114
|
+
signatureBase64: String,
|
|
115
|
+
publicKeyBase64: String,
|
|
116
|
+
strictSignature: Boolean = true
|
|
117
|
+
): VerifyResult {
|
|
118
|
+
if (expectedMd5.isEmpty()) {
|
|
119
|
+
Log.w(TAG, "expected md5 empty, skip md5 & signature verify")
|
|
120
|
+
return VerifyResult.OK
|
|
121
|
+
}
|
|
122
|
+
val actualMd5 = md5(file)
|
|
123
|
+
if (!actualMd5.equals(expectedMd5, ignoreCase = true)) {
|
|
124
|
+
Log.e(TAG, "md5 mismatch: expected=$expectedMd5, actual=$actualMd5")
|
|
125
|
+
return VerifyResult.MD5_MISMATCH
|
|
126
|
+
}
|
|
127
|
+
return if (verifySignatureOnly(
|
|
128
|
+
actualMd5.lowercase(),
|
|
129
|
+
signatureBase64,
|
|
130
|
+
publicKeyBase64,
|
|
131
|
+
strictSignature
|
|
132
|
+
)
|
|
133
|
+
) {
|
|
134
|
+
VerifyResult.OK
|
|
135
|
+
} else {
|
|
136
|
+
VerifyResult.SIGNATURE_INVALID
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 完整校验:MD5 + 可选 Ed25519。
|
|
142
|
+
*
|
|
143
|
+
* @param file 已下载的补丁文件
|
|
144
|
+
* @param expectedMd5 manifest 中的预期 MD5(小写 hex)
|
|
145
|
+
* @param signatureBase64 manifest 中的 Ed25519 签名(Base64,允许为空)
|
|
146
|
+
* @param publicKeyBase64 X.509 SubjectPublicKeyInfo 的 Base64 公钥(允许为空)
|
|
147
|
+
* @param strictSignature API < 33 是否拒绝签名校验(默认 true,安全)
|
|
148
|
+
* @return 是否通过校验
|
|
149
|
+
*/
|
|
150
|
+
fun verify(
|
|
151
|
+
file: File,
|
|
152
|
+
expectedMd5: String,
|
|
153
|
+
signatureBase64: String,
|
|
154
|
+
publicKeyBase64: String,
|
|
155
|
+
strictSignature: Boolean = true
|
|
156
|
+
): Boolean = verifyDetailed(
|
|
157
|
+
file, expectedMd5, signatureBase64, publicKeyBase64, strictSignature
|
|
158
|
+
) == VerifyResult.OK
|
|
159
|
+
|
|
160
|
+
private fun verifyEd25519(
|
|
161
|
+
md5Hex: String,
|
|
162
|
+
signatureBase64: String,
|
|
163
|
+
publicKeyBase64: String
|
|
164
|
+
): Boolean {
|
|
165
|
+
val pkBytes = Base64.decode(publicKeyBase64, Base64.NO_WRAP)
|
|
166
|
+
val keySpec = X509EncodedKeySpec(pkBytes)
|
|
167
|
+
val kf = KeyFactory.getInstance("Ed25519")
|
|
168
|
+
val pk = kf.generatePublic(keySpec)
|
|
169
|
+
|
|
170
|
+
val sig = Signature.getInstance("Ed25519")
|
|
171
|
+
sig.initVerify(pk)
|
|
172
|
+
sig.update(md5Hex.toByteArray(Charsets.UTF_8))
|
|
173
|
+
|
|
174
|
+
val sigBytes = Base64.decode(signatureBase64, Base64.NO_WRAP)
|
|
175
|
+
return sig.verify(sigBytes)
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import kotlin.test.Test
|
|
4
|
+
import kotlin.test.assertFalse
|
|
5
|
+
import kotlin.test.assertTrue
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Unit tests for the boot-window crash attribution rule that prevents a
|
|
9
|
+
* healthy OTA patch from being reverted by an unrelated runtime crash/ANR
|
|
10
|
+
* hours into a session.
|
|
11
|
+
*/
|
|
12
|
+
class CrashGuardTest {
|
|
13
|
+
|
|
14
|
+
private val window = PatcherConfig.BOOT_CRASH_WINDOW_MS
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun crashWithinWindowIsChargedToPatch() {
|
|
18
|
+
val bootAt = 1_000_000L
|
|
19
|
+
// Crash 5s after boot — clearly a boot failure.
|
|
20
|
+
assertTrue(CrashGuard.isWithinBootWindow(bootAt, bootAt + 5_000L))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
fun crashAtExactWindowEdgeIsCharged() {
|
|
25
|
+
val bootAt = 1_000_000L
|
|
26
|
+
assertTrue(CrashGuard.isWithinBootWindow(bootAt, bootAt + window))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Test
|
|
30
|
+
fun crashLongAfterBootIsNotChargedToPatch() {
|
|
31
|
+
val bootAt = 1_000_000L
|
|
32
|
+
// Crash 2 hours into a healthy session — must NOT revert the patch.
|
|
33
|
+
val twoHours = 2 * 60 * 60 * 1000L
|
|
34
|
+
assertFalse(CrashGuard.isWithinBootWindow(bootAt, bootAt + twoHours))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test
|
|
38
|
+
fun crashJustPastWindowIsNotCharged() {
|
|
39
|
+
val bootAt = 1_000_000L
|
|
40
|
+
assertFalse(CrashGuard.isWithinBootWindow(bootAt, bootAt + window + 1L))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
fun unknownBootTimestampFallsBackToCharging() {
|
|
45
|
+
// No boot timestamp recorded (legacy state / upgrade) → conservative,
|
|
46
|
+
// preserve prior fail-fast behavior.
|
|
47
|
+
assertTrue(CrashGuard.isWithinBootWindow(0L, 5_000_000L))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
fun unknownCrashTimestampFallsBackToCharging() {
|
|
52
|
+
assertTrue(CrashGuard.isWithinBootWindow(1_000_000L, 0L))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun clockSkewNegativeElapsedTreatedAsInWindow() {
|
|
57
|
+
// ExitInfo timestamp earlier than recorded boot (device clock change) →
|
|
58
|
+
// treat as in-window rather than silently ignoring a genuine boot crash.
|
|
59
|
+
assertTrue(CrashGuard.isWithinBootWindow(2_000_000L, 1_000_000L))
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.flutter_patcher.flutter_patcher
|
|
2
|
+
|
|
3
|
+
import kotlin.test.Test
|
|
4
|
+
import kotlin.test.assertEquals
|
|
5
|
+
|
|
6
|
+
class LoaderHookTest {
|
|
7
|
+
@Test
|
|
8
|
+
fun patchedAssetLookupKeysUseBundlePath() {
|
|
9
|
+
assertEquals(
|
|
10
|
+
"flutter_patcher_assets/images/hero.png",
|
|
11
|
+
patchedAssetLookupKey(
|
|
12
|
+
"flutter_patcher_assets",
|
|
13
|
+
"images/hero.png"
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
assertEquals(
|
|
17
|
+
"flutter_patcher_assets/packages/demo/images/hero.png",
|
|
18
|
+
patchedPackageAssetLookupKey(
|
|
19
|
+
"flutter_patcher_assets",
|
|
20
|
+
"images/hero.png",
|
|
21
|
+
"demo"
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|