@_nazmiforreal/flutter-ota 0.1.26 → 0.1.28

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.
Files changed (259) hide show
  1. package/README.md +112 -18
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/bin/flutter-ota.js +12 -11
  4. package/dart-src/packages/cli-tools/README.md +146 -2
  5. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
  9. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
  10. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +36 -8
  11. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
  12. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
  13. package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
  14. package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
  15. package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
  16. package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
  17. package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
  18. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  19. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  20. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
  21. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
  22. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
  23. package/dart-src/root/README.md +334 -0
  24. package/dart-src/root/analysis_options.yaml +21 -0
  25. package/dart-src/root/android/build.gradle +116 -0
  26. package/dart-src/root/android/consumer-rules.pro +23 -0
  27. package/dart-src/root/android/settings.gradle +1 -0
  28. package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
  29. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
  30. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
  31. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
  32. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
  33. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
  34. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
  35. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
  36. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
  37. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
  38. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
  39. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
  40. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
  41. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
  42. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
  43. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
  44. package/dart-src/root/bin/pack.dart +182 -0
  45. package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
  46. package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
  47. package/dart-src/root/lib/src/aws_update_source.dart +97 -0
  48. package/dart-src/root/lib/src/blacklist.dart +54 -0
  49. package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
  50. package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
  51. package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
  52. package/dart-src/root/lib/src/io.dart +48 -0
  53. package/dart-src/root/lib/src/io_stub.dart +19 -0
  54. package/dart-src/root/lib/src/ota_progress_overlay.dart +1186 -0
  55. package/dart-src/root/lib/src/pack/pack.dart +434 -0
  56. package/dart-src/root/lib/src/patch_info.dart +12 -0
  57. package/dart-src/root/lib/src/patcher_channel.dart +160 -0
  58. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
  59. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
  60. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
  61. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
  62. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
  63. package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
  64. package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
  65. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
  66. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
  67. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
  68. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
  69. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
  70. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
  71. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
  72. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
  73. package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
  74. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
  75. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
  76. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
  77. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
  78. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
  79. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
  80. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
  81. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
  82. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
  83. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
  84. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
  85. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
  86. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
  87. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
  88. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
  89. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
  90. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
  91. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
  92. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
  93. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
  94. package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
  95. package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
  96. package/dart-src/root/lib/src/shared_update_check.dart +131 -0
  97. package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
  98. package/dart-src/root/pubspec.yaml +54 -0
  99. package/package.json +1 -1
  100. package/scripts/postinstall.js +17 -13
  101. package/scripts/vendor.sh +22 -7
  102. package/dart-src/packages/core/CHANGELOG.md +0 -21
  103. package/dart-src/packages/core/README.md +0 -23
  104. package/dart-src/packages/core/example/lib/main.dart +0 -69
  105. package/dart-src/packages/core/pubspec.yaml +0 -13
  106. package/dart-src/packages/core/test/rollout_test.dart +0 -135
  107. package/dart-src/packages/core/test/semver_test.dart +0 -55
  108. package/dart-src/packages/core/test/semver_vectors.json +0 -1
  109. package/dart-src/packages/core/test/types_test.dart +0 -131
  110. package/dart-src/packages/core/test/uuid_test.dart +0 -40
  111. package/dart-src/packages/core/tool/dbg.dart +0 -7
  112. package/dart-src/plugins/aws/.gitkeep +0 -0
  113. package/dart-src/plugins/aws/CHANGELOG.md +0 -23
  114. package/dart-src/plugins/aws/LICENSE +0 -21
  115. package/dart-src/plugins/aws/README.md +0 -28
  116. package/dart-src/plugins/aws/example/lib/main.dart +0 -60
  117. package/dart-src/plugins/aws/pubspec.yaml +0 -30
  118. package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
  119. package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
  120. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
  121. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  122. package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
  123. package/dart-src/plugins/cloudflare/LICENSE +0 -21
  124. package/dart-src/plugins/cloudflare/README.md +0 -25
  125. package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
  126. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
  127. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
  128. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
  129. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
  130. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
  131. package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
  132. package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
  133. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
  134. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
  135. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
  136. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
  137. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
  138. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
  139. package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
  140. package/dart-src/plugins/plugin-core/LICENSE +0 -21
  141. package/dart-src/plugins/plugin-core/README.md +0 -21
  142. package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
  143. package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
  144. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
  145. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
  146. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
  147. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
  148. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
  149. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
  150. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
  151. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
  152. package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
  153. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
  154. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
  155. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
  156. package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
  157. package/dart-src/plugins/pocketbase/LICENSE +0 -21
  158. package/dart-src/plugins/pocketbase/README.md +0 -54
  159. package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
  160. package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
  161. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
  162. package/dart-src/plugins/postgres/.gitkeep +0 -0
  163. package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
  164. package/dart-src/plugins/postgres/LICENSE +0 -21
  165. package/dart-src/plugins/postgres/README.md +0 -29
  166. package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
  167. package/dart-src/plugins/postgres/pubspec.yaml +0 -26
  168. package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
  169. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
  170. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
  171. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
  172. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
  173. package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
  174. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
  175. package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
  176. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
  177. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
  178. package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
  179. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
  180. package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
  181. package/dart-src/plugins/supabase/LICENSE +0 -21
  182. package/dart-src/plugins/supabase/README.md +0 -53
  183. package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
  184. package/dart-src/plugins/supabase/pubspec.yaml +0 -27
  185. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
  186. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
  187. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
  188. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
  189. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
  190. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
  191. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
  192. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  193. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  194. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
  195. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
  196. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
  197. package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
  198. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
  199. /package/dart-src/{packages/core → root}/LICENSE +0 -0
  200. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
  201. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
  202. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
  203. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
  204. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
  205. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
  206. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
  207. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
  208. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
  209. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
  210. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
  211. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
  212. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
  213. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
  214. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
  215. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
  216. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
  217. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
  218. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
  219. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
  220. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
  221. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
  222. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
  223. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
  224. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
  225. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
  226. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
  227. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
  228. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
  229. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
  230. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
  231. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
  232. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
  233. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
  234. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
  235. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
  236. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
  237. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
  238. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
  239. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
  240. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
  241. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
  242. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
  243. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
  244. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
  245. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
  246. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
  247. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
  248. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
  249. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
  250. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
  251. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
  252. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
  253. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
  254. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
  255. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
  256. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
  257. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
  258. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
  259. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
@@ -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>
@@ -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
+ }
@@ -0,0 +1,154 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import org.json.JSONArray
6
+ import org.json.JSONObject
7
+
8
+ /**
9
+ * 已知"装上就出事"的补丁本地黑名单。
10
+ *
11
+ * # 为什么需要
12
+ * 没有黑名单时,若服务端持续下发同一个有问题的补丁:
13
+ * 客户端下载 → 校验通过 → 装上 → 启动崩溃 → 自动回滚 → 下次又收到 → 死循环。
14
+ * 黑名单把"已经验证过的坏补丁"记下来,再次下发直接拒,连流量都不浪费。
15
+ *
16
+ * # 双键设计 (version, md5)
17
+ * - 只用 `version`:开发者修了 bug 后用同样 version 重发会被无脑拒绝
18
+ * - 只用 `md5`:可读性差,debug 时不知道是哪个补丁
19
+ * - 用 (version, md5) 同时作为复合键:不同 md5 视作"修了再试",允许下载
20
+ *
21
+ * # 持久性
22
+ * **不**因 APK 版本升级清空。否则线上事故时若服务端配置忘下架,升 APK 后又
23
+ * 能下到坏补丁。除非用户主动调 [clear],黑名单条目永久保留。
24
+ *
25
+ * # 容量
26
+ * FIFO 上限 [MAX_ENTRIES]=50,超出按时间淘汰最老条目。极端情况下避免无限增长。
27
+ */
28
+ internal object BlacklistStore {
29
+
30
+ private const val TAG = "FlutterPatcher/Blacklist"
31
+
32
+ /** SharedPreferences key(沿用 PatcherConfig.PREFS_NAME 文件,与现有键无冲突)。*/
33
+ private const val KEY_BLACKLIST = "patch_blacklist"
34
+
35
+ /** 黑名单条目数上限,达到后 FIFO 淘汰最早入黑的条目。*/
36
+ const val MAX_ENTRIES = 50
37
+
38
+ // ---- 入黑原因常量(与 BootDiagnosticStore 命名风格保持一致)----
39
+
40
+ const val REASON_BOOT_CRASH = "BOOT_CRASH"
41
+ const val REASON_MD5_MISMATCH = "MD5_MISMATCH"
42
+ const val REASON_SIGNATURE_INVALID = "SIGNATURE_INVALID"
43
+ const val REASON_META_CORRUPTED = "META_CORRUPTED"
44
+ const val REASON_APPLY_FAILED = "APPLY_FAILED"
45
+
46
+ // ---- 字段 key ----
47
+
48
+ private const val FIELD_VERSION = "version"
49
+ private const val FIELD_MD5 = "md5"
50
+ private const val FIELD_REASON = "reason"
51
+ private const val FIELD_BLACKLISTED_AT = "blacklistedAt"
52
+
53
+ /**
54
+ * 把 (version, md5) 加入黑名单。重复 add 会更新 reason + 时间戳,不会出现重复条目。
55
+ * version 或 md5 为空字符串则忽略(防御性,不进黑名单)。
56
+ */
57
+ @Synchronized
58
+ fun add(context: Context, version: String, md5: String, reason: String) {
59
+ if (version.isEmpty() || md5.isEmpty()) {
60
+ Log.w(TAG, "skip blacklist entry: version='$version' md5='$md5'")
61
+ return
62
+ }
63
+ val arr = readArray(context)
64
+ // 找到旧条目并删除(按双键),再追加到末尾,达到"重复 add 时刷新位置 + 元数据"的语义
65
+ val md5Lower = md5.lowercase()
66
+ val filtered = JSONArray()
67
+ for (i in 0 until arr.length()) {
68
+ val obj = arr.optJSONObject(i) ?: continue
69
+ val v = obj.optString(FIELD_VERSION)
70
+ val m = obj.optString(FIELD_MD5)
71
+ if (v == version && m == md5Lower) continue
72
+ filtered.put(obj)
73
+ }
74
+ filtered.put(JSONObject().apply {
75
+ put(FIELD_VERSION, version)
76
+ put(FIELD_MD5, md5Lower)
77
+ put(FIELD_REASON, reason)
78
+ put(FIELD_BLACKLISTED_AT, System.currentTimeMillis())
79
+ })
80
+ // FIFO 淘汰:超过上限就丢最老的
81
+ val capped = if (filtered.length() > MAX_ENTRIES) {
82
+ val drop = filtered.length() - MAX_ENTRIES
83
+ val sliced = JSONArray()
84
+ for (i in drop until filtered.length()) sliced.put(filtered.opt(i))
85
+ sliced
86
+ } else {
87
+ filtered
88
+ }
89
+ PatcherConfig.prefs(context).edit()
90
+ .putString(KEY_BLACKLIST, capped.toString())
91
+ .commit()
92
+ Log.w(TAG, "blacklisted version=$version md5=$md5Lower reason=$reason (${capped.length()}/$MAX_ENTRIES)")
93
+ }
94
+
95
+ /** 命中 (version, md5) 双键则返回 true。md5 大小写不敏感比较。*/
96
+ fun contains(context: Context, version: String, md5: String): Boolean {
97
+ if (version.isEmpty() || md5.isEmpty()) return false
98
+ val arr = readArray(context)
99
+ val md5Lower = md5.lowercase()
100
+ for (i in 0 until arr.length()) {
101
+ val obj = arr.optJSONObject(i) ?: continue
102
+ if (obj.optString(FIELD_VERSION) == version &&
103
+ obj.optString(FIELD_MD5).equals(md5Lower, ignoreCase = true)
104
+ ) {
105
+ return true
106
+ }
107
+ }
108
+ return false
109
+ }
110
+
111
+ /**
112
+ * 仅以 version 维度命中黑名单。供"调用方未下发 md5"场景的下载前置检查。
113
+ * 同 version 任意 md5 条目都视为命中。
114
+ */
115
+ fun containsByVersion(context: Context, version: String): Boolean {
116
+ if (version.isEmpty()) return false
117
+ val arr = readArray(context)
118
+ for (i in 0 until arr.length()) {
119
+ val obj = arr.optJSONObject(i) ?: continue
120
+ if (obj.optString(FIELD_VERSION) == version) return true
121
+ }
122
+ return false
123
+ }
124
+
125
+ /** 全部黑名单条目(旧→新顺序)。供 Dart 业务侧查询展示用。*/
126
+ fun entries(context: Context): List<Map<String, Any?>> {
127
+ val arr = readArray(context)
128
+ return List(arr.length()) { i ->
129
+ val obj = arr.optJSONObject(i)
130
+ mapOf(
131
+ FIELD_VERSION to obj?.optString(FIELD_VERSION),
132
+ FIELD_MD5 to obj?.optString(FIELD_MD5),
133
+ FIELD_REASON to obj?.optString(FIELD_REASON),
134
+ FIELD_BLACKLISTED_AT to (obj?.optLong(FIELD_BLACKLISTED_AT) ?: 0L),
135
+ )
136
+ }
137
+ }
138
+
139
+ /** 清空整个黑名单。慎用:通常只在调试时调用。*/
140
+ fun clear(context: Context) {
141
+ PatcherConfig.prefs(context).edit().remove(KEY_BLACKLIST).commit()
142
+ Log.w(TAG, "blacklist cleared")
143
+ }
144
+
145
+ private fun readArray(context: Context): JSONArray {
146
+ val raw = PatcherConfig.prefs(context).getString(KEY_BLACKLIST, null) ?: return JSONArray()
147
+ return try {
148
+ JSONArray(raw)
149
+ } catch (e: Exception) {
150
+ Log.w(TAG, "blacklist json corrupt, resetting", e)
151
+ JSONArray()
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,150 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import org.json.JSONArray
6
+ import org.json.JSONObject
7
+
8
+ /**
9
+ * 上次冷启动时补丁加载结果的持久化存储。
10
+ *
11
+ * 现状:补丁能否生效取决于 `CrashGuard` / `PatchManager.getValidPatchPath` /
12
+ * `LoaderHook.install` 三处决策,事件只写 logcat。线上业务只能事后通过
13
+ * [PatchManager.currentVersion] 间接推测"补丁是不是又没了",监控/告警接不上。
14
+ *
15
+ * 本对象在每次冷启动时把"补丁的加载结果 + 上下文"一次性覆写到
16
+ * SharedPreferences,Dart 侧通过 `FlutterPatcher.lastBootDiagnostic` 查询。
17
+ *
18
+ * # 写入时机
19
+ * - [FlutterPatcherApplication.attachPatcher] 三个出口(patched / hookFailed / noPatch / unknown)
20
+ * - [PatchManager.getValidPatchPath] 通过传入的 onDrop 闭包写入(versionCode/md5/sig/meta)
21
+ * - [CrashGuard.shouldLoadPatch] 通过传入的 onDrop 闭包写入(circuit breaker)
22
+ *
23
+ * # 跨边界约定
24
+ * status 用大写 SCREAMING_SNAKE 字符串而非 int,保证 Kotlin / Dart 两侧可读性 +
25
+ * 未来扩展不破坏序列化兼容。Dart 侧用 [PatchBootStatus] 枚举映射。
26
+ */
27
+ internal object BootDiagnosticStore {
28
+
29
+ private const val TAG = "FlutterPatcher/Diag"
30
+
31
+ /** SharedPreferences key(沿用 PatcherConfig.PREFS_NAME 文件,与现有键无冲突)。*/
32
+ private const val KEY_LAST_BOOT_DIAG = "last_boot_diagnostic"
33
+
34
+ // ---- status 常量(与 Dart 侧 PatchBootStatus 一一对应)----
35
+
36
+ /** 未安装补丁,使用 APK 内置 libapp.so。属于正常状态。*/
37
+ const val NO_PATCH = "NO_PATCH"
38
+
39
+ /** 补丁加载成功,本次启动按补丁运行。*/
40
+ const val PATCHED = "PATCHED"
41
+
42
+ /** 补丁被丢弃:targetVersionCode 与当前 APK versionCode 不匹配。*/
43
+ const val DROPPED_VERSION_CODE_MISMATCH = "DROPPED_VERSION_CODE_MISMATCH"
44
+
45
+ /** 补丁被丢弃:本地 .so 文件 md5 与 meta.effectiveMd5 不一致。*/
46
+ const val DROPPED_MD5_MISMATCH = "DROPPED_MD5_MISMATCH"
47
+
48
+ /** 补丁被丢弃:Ed25519 签名校验失败 / 严格模式下 API < 33。*/
49
+ const val DROPPED_SIGNATURE_INVALID = "DROPPED_SIGNATURE_INVALID"
50
+
51
+ /** 补丁被丢弃:meta.json 损坏或缺关键字段(effectiveMd5 等)。*/
52
+ const val DROPPED_META_CORRUPTED = "DROPPED_META_CORRUPTED"
53
+
54
+ /** 补丁被丢弃:连续启动失败累计 >= maxCrashCount,熔断器触发。*/
55
+ const val DROPPED_CIRCUIT_BREAKER = "DROPPED_CIRCUIT_BREAKER"
56
+
57
+ /** 补丁文件保留,但反射替换 FlutterLoader 失败,本次启动用了内置 .so。*/
58
+ const val HOOK_INSTALL_FAILED = "HOOK_INSTALL_FAILED"
59
+
60
+ /** attachPatcher 阶段抛出未分类异常。*/
61
+ const val UNKNOWN = "UNKNOWN"
62
+
63
+ // ---- 字段 key(写入 / 读取共用)----
64
+
65
+ private const val FIELD_STATUS = "status"
66
+ private const val FIELD_PATCH_VERSION = "patchVersion"
67
+ private const val FIELD_PATCH_TARGET_VC = "patchTargetVersionCode"
68
+ private const val FIELD_APP_VC = "appVersionCode"
69
+ private const val FIELD_CRASH_COUNT = "crashCount"
70
+ private const val FIELD_ATTEMPTED_LOADER_FIELDS = "attemptedLoaderFields"
71
+ private const val FIELD_MESSAGE = "message"
72
+ private const val FIELD_RECORDED_AT = "recordedAt"
73
+
74
+ /**
75
+ * 覆写一条诊断记录。
76
+ *
77
+ * 使用 commit 同步写入:本方法常在 attachBaseContext 阶段调用,若进程随后
78
+ * 因补丁问题崩溃,commit 能保证下次启动 Dart 侧仍能查询到本次的诊断结果。
79
+ */
80
+ fun record(
81
+ context: Context,
82
+ status: String,
83
+ patchVersion: String? = null,
84
+ patchTargetVersionCode: Long? = null,
85
+ appVersionCode: Long? = null,
86
+ crashCount: Int? = null,
87
+ attemptedLoaderFields: List<String>? = null,
88
+ message: String? = null,
89
+ ) {
90
+ val json = JSONObject().apply {
91
+ put(FIELD_STATUS, status)
92
+ putOpt(FIELD_PATCH_VERSION, patchVersion)
93
+ putOpt(FIELD_PATCH_TARGET_VC, patchTargetVersionCode)
94
+ putOpt(FIELD_APP_VC, appVersionCode)
95
+ putOpt(FIELD_CRASH_COUNT, crashCount)
96
+ if (attemptedLoaderFields != null) {
97
+ put(FIELD_ATTEMPTED_LOADER_FIELDS, JSONArray(attemptedLoaderFields))
98
+ }
99
+ putOpt(FIELD_MESSAGE, message)
100
+ put(FIELD_RECORDED_AT, System.currentTimeMillis())
101
+ }
102
+ PatcherConfig.prefs(context).edit()
103
+ .putString(KEY_LAST_BOOT_DIAG, json.toString())
104
+ .commit()
105
+ Log.d(TAG, "record status=$status patch=$patchVersion msg=$message")
106
+ }
107
+
108
+ /**
109
+ * 读取最近一次 record。
110
+ *
111
+ * @return 诊断 Map,可直接交给 MethodChannel 发到 Dart 侧;
112
+ * null 表示从未 record 过(首次安装或 pm clear 后第一次启动且尚未到首帧)。
113
+ */
114
+ fun read(context: Context): Map<String, Any?>? {
115
+ val raw = PatcherConfig.prefs(context).getString(KEY_LAST_BOOT_DIAG, null) ?: return null
116
+ return try {
117
+ val json = JSONObject(raw)
118
+ val map = mutableMapOf<String, Any?>()
119
+ map[FIELD_STATUS] = json.optString(FIELD_STATUS, UNKNOWN)
120
+ map[FIELD_PATCH_VERSION] = json.optStringOrNull(FIELD_PATCH_VERSION)
121
+ map[FIELD_PATCH_TARGET_VC] = json.optLongOrNull(FIELD_PATCH_TARGET_VC)
122
+ map[FIELD_APP_VC] = json.optLongOrNull(FIELD_APP_VC)
123
+ map[FIELD_CRASH_COUNT] = json.optIntOrNull(FIELD_CRASH_COUNT)
124
+ map[FIELD_ATTEMPTED_LOADER_FIELDS] = json.optStringListOrNull(FIELD_ATTEMPTED_LOADER_FIELDS)
125
+ map[FIELD_MESSAGE] = json.optStringOrNull(FIELD_MESSAGE)
126
+ map[FIELD_RECORDED_AT] = json.optLong(FIELD_RECORDED_AT, 0L)
127
+ map
128
+ } catch (e: Exception) {
129
+ Log.w(TAG, "read failed, raw=$raw", e)
130
+ null
131
+ }
132
+ }
133
+
134
+ // ---- 内部工具:JSONObject 缺字段时返回 null 而非 "" / 0 ----
135
+
136
+ private fun JSONObject.optStringOrNull(key: String): String? =
137
+ if (isNull(key) || !has(key)) null else optString(key, "").ifEmpty { null }
138
+
139
+ private fun JSONObject.optLongOrNull(key: String): Long? =
140
+ if (isNull(key) || !has(key)) null else optLong(key)
141
+
142
+ private fun JSONObject.optIntOrNull(key: String): Int? =
143
+ if (isNull(key) || !has(key)) null else optInt(key)
144
+
145
+ private fun JSONObject.optStringListOrNull(key: String): List<String>? {
146
+ if (isNull(key) || !has(key)) return null
147
+ val arr = optJSONArray(key) ?: return null
148
+ return List(arr.length()) { arr.optString(it) }
149
+ }
150
+ }