@_nazmiforreal/flutter-ota 0.1.0 → 0.1.2

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 (167) hide show
  1. package/bin/flutter-patcher.js +48 -39
  2. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  3. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  4. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  5. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  6. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  21. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  22. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  23. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  24. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  25. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  30. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  31. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  38. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  39. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  40. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  41. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  42. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  43. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  44. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  45. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  46. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  47. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  48. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  49. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  50. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  51. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  52. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  53. package/dart-src/packages/core/lib/src/status.dart +24 -0
  54. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  55. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  56. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  57. package/dart-src/packages/core/pubspec.yaml +9 -0
  58. package/dart-src/packages/core/tool/dbg.dart +6 -0
  59. package/dart-src/plugins/aws/.gitkeep +0 -0
  60. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  61. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  62. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  63. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  64. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  65. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  66. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  67. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  68. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  69. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  70. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  71. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  72. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  73. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  74. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  75. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  76. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  77. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  78. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  79. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  81. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  82. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  83. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  84. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  85. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  86. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  87. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  88. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  89. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  90. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  91. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  92. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  93. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  94. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  95. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  96. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  97. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  98. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  99. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  100. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  101. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  102. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  103. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  104. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  105. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  106. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  107. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  108. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  109. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  110. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  111. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  112. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  113. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  114. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  115. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  116. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  117. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  118. package/dart-src/plugins/postgres/.gitkeep +0 -0
  119. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  120. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  121. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  122. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  123. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  124. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  125. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  126. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  127. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  128. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  129. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  130. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  131. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  132. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  133. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  134. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  135. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  136. package/dart-src/plugins/standalone/.gitkeep +0 -0
  137. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  138. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  139. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  140. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  141. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  142. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  143. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  144. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  145. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  146. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  147. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  148. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  149. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  150. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  151. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  152. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  153. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  154. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  155. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  156. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  157. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  158. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  159. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  160. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  161. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  162. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  163. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  164. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  165. package/package.json +2 -1
  166. package/scripts/postinstall.js +53 -25
  167. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
@@ -0,0 +1,111 @@
1
+ import 'changed_asset.dart' show ChangedAsset;
2
+ import 'status.dart';
3
+
4
+ /// hot-updater `UpdateInfo` — database-layer update decision.
5
+ class UpdateInfo {
6
+ final String id;
7
+ final bool shouldForceUpdate;
8
+ final String? message;
9
+ final UpdateStatus status;
10
+ final String? storageUri;
11
+ final String? fileHash;
12
+
13
+ /// Internal rollout metadata; never serialized to update-check clients.
14
+ final int? rolloutCohortCount;
15
+ final List<String>? targetCohorts;
16
+
17
+ const UpdateInfo({
18
+ required this.id,
19
+ required this.shouldForceUpdate,
20
+ required this.message,
21
+ required this.status,
22
+ required this.storageUri,
23
+ required this.fileHash,
24
+ this.rolloutCohortCount,
25
+ this.targetCohorts,
26
+ });
27
+ }
28
+
29
+ /// Device-facing payload for an available update —
30
+ /// hot-updater `AppUpdateAvailableInfo`.
31
+ class AppUpdateAvailableInfo extends AppUpdateInfo {
32
+ final String id;
33
+ final bool shouldForceUpdate;
34
+ final String? message;
35
+ final UpdateStatus status;
36
+ final String? fileUrl;
37
+ final String? fileHash;
38
+
39
+ /// Ed25519 (base64) signature over the artifact MD5 hex string. Consumed by
40
+ /// the device SDK to verify the patch (`PatchInfo.signature`).
41
+ final String? signature;
42
+
43
+ final String? manifestUrl;
44
+ final String? manifestFileHash;
45
+ final Map<String, ChangedAsset>? changedAssets;
46
+
47
+ const AppUpdateAvailableInfo({
48
+ required this.id,
49
+ required this.shouldForceUpdate,
50
+ required this.message,
51
+ required this.status,
52
+ required this.fileUrl,
53
+ required this.fileHash,
54
+ this.signature,
55
+ this.manifestUrl,
56
+ this.manifestFileHash,
57
+ this.changedAssets,
58
+ });
59
+
60
+ factory AppUpdateAvailableInfo.fromJson(Map<String, dynamic> j) =>
61
+ AppUpdateAvailableInfo(
62
+ id: j['id'] as String,
63
+ shouldForceUpdate: (j['shouldForceUpdate'] ?? false) as bool,
64
+ message: j['message'] as String?,
65
+ status: UpdateStatus.fromValue(j['status'] as String),
66
+ fileUrl: j['fileUrl'] as String?,
67
+ fileHash: j['fileHash'] as String?,
68
+ signature: j['signature'] as String?,
69
+ manifestUrl: j['manifestUrl'] as String?,
70
+ manifestFileHash: j['manifestFileHash'] as String?,
71
+ changedAssets: (j['changedAssets'] as Map?)?.map((k, v) => MapEntry(
72
+ k as String,
73
+ ChangedAsset.fromJson((v as Map).cast<String, dynamic>()),
74
+ )),
75
+ );
76
+
77
+ @override
78
+ Map<String, dynamic> toJson() => {
79
+ 'id': id,
80
+ 'shouldForceUpdate': shouldForceUpdate,
81
+ 'message': message,
82
+ 'status': status.value,
83
+ 'fileUrl': fileUrl,
84
+ 'fileHash': fileHash,
85
+ if (signature != null) 'signature': signature,
86
+ if (manifestUrl != null) 'manifestUrl': manifestUrl,
87
+ if (manifestFileHash != null) 'manifestFileHash': manifestFileHash,
88
+ if (changedAssets != null) 'changedAssets': changedAssets,
89
+ };
90
+ }
91
+
92
+ /// Device-facing "nothing to do" — hot-updater `AppUpToDateInfo`.
93
+ class AppUpToDateInfo extends AppUpdateInfo {
94
+ const AppUpToDateInfo();
95
+
96
+ @override
97
+ Map<String, dynamic> toJson() => {'status': 'UP_TO_DATE'};
98
+ }
99
+
100
+ /// Sealed union: hot-updater `AppUpdateInfo`.
101
+ sealed class AppUpdateInfo {
102
+ const AppUpdateInfo();
103
+
104
+ Map<String, dynamic> toJson();
105
+
106
+ factory AppUpdateInfo.fromJson(Map<String, dynamic> j) {
107
+ final raw = j['status'];
108
+ if (raw == 'UP_TO_DATE') return const AppUpToDateInfo();
109
+ return AppUpdateAvailableInfo.fromJson(j);
110
+ }
111
+ }
@@ -0,0 +1,191 @@
1
+ import 'metadata.dart';
2
+ import 'bundle_patch_artifact.dart';
3
+ import 'platform.dart';
4
+ import 'uuid.dart' show nilUuid;
5
+
6
+ /// hot-updater `Bundle` — the central database row / manifest object.
7
+ ///
8
+ /// Wire format is camelCase; database rows are snake_case. [Bundle.fromJson]
9
+ /// tolerates both so one model works against PostgREST rows and API payloads.
10
+ class Bundle {
11
+ final String id; // uuidv7
12
+
13
+ final Platform platform;
14
+ final bool shouldForceUpdate;
15
+ final bool enabled;
16
+
17
+ /// MD5 hex of the artifact blob. The device SDK verifies the downloaded
18
+ /// file against this value (see [SignatureVerifier]). When the bundle is
19
+ /// signed, the Ed25519 signature lives in [BundleMetadata.signature]
20
+ /// (carried in the update-check `signature` field), not here.
21
+ final String fileHash;
22
+
23
+ /// Protocol URI, e.g. supabase-storage://bucket/bundles/<id>/patch.zip.
24
+ final String storageUri;
25
+ final String? gitCommitHash;
26
+ final String? message;
27
+
28
+ /// Default "production".
29
+ final String channel;
30
+
31
+ /// Semver range; XOR with [fingerprintHash] per DB CHECK constraint.
32
+ final String? targetAppVersion;
33
+ final String? fingerprintHash;
34
+ final BundleMetadata? metadata;
35
+ final String? manifestStorageUri;
36
+ final String? manifestFileHash;
37
+ final String? assetBaseStorageUri;
38
+
39
+ /// Binary patch artifacts, array order = precedence.
40
+ final List<BundlePatchArtifact>? patches;
41
+
42
+ // Deprecated single-patch fields (kept for wire compatibility).
43
+ final String? patchBaseBundleId;
44
+ final String? patchBaseFileHash;
45
+ final String? patchFileHashLegacy;
46
+ final String? patchStorageUri;
47
+
48
+ /// Per-mille 0..1000; null = full rollout.
49
+ final int? rolloutCohortCount;
50
+
51
+ /// Stored in DB, never returned to update-check clients.
52
+ final List<String>? targetCohorts;
53
+
54
+ const Bundle({
55
+ required this.id,
56
+ required this.platform,
57
+ required this.shouldForceUpdate,
58
+ required this.enabled,
59
+ required this.fileHash,
60
+ required this.storageUri,
61
+ required this.channel,
62
+ this.gitCommitHash,
63
+ this.message,
64
+ this.targetAppVersion,
65
+ this.fingerprintHash,
66
+ this.metadata,
67
+ this.manifestStorageUri,
68
+ this.manifestFileHash,
69
+ this.assetBaseStorageUri,
70
+ this.patches,
71
+ this.patchBaseBundleId,
72
+ this.patchBaseFileHash,
73
+ String? patchFileHash,
74
+ this.patchStorageUri,
75
+ this.rolloutCohortCount,
76
+ this.targetCohorts,
77
+ }) : patchFileHashLegacy = patchFileHash;
78
+
79
+ static T? _s<T>(Map<String, dynamic> j, String camel, String snake) {
80
+ final v = j[camel] ?? j[snake];
81
+ return v == null ? null : v as T;
82
+ }
83
+
84
+ factory Bundle.fromJson(Map<String, dynamic> j) => Bundle(
85
+ id: (j['id'] ?? nilUuid) as String,
86
+ platform: Platform.fromValue(j['platform'] as String),
87
+ shouldForceUpdate:
88
+ (j['shouldForceUpdate'] ?? j['should_force_update'] ?? false)
89
+ as bool,
90
+ enabled: (j['enabled'] ?? false) as bool,
91
+ fileHash: (j['fileHash'] ?? j['file_hash']) as String,
92
+ storageUri: (j['storageUri'] ?? j['storage_uri']) as String,
93
+ gitCommitHash: _s<String>(j, 'gitCommitHash', 'git_commit_hash'),
94
+ message: j['message'] == null ? null : j['message'] as String,
95
+ channel: (j['channel'] ?? 'production') as String,
96
+ targetAppVersion:
97
+ _s<String>(j, 'targetAppVersion', 'target_app_version'),
98
+ fingerprintHash: _s<String>(j, 'fingerprintHash', 'fingerprint_hash'),
99
+ metadata: j['metadata'] is Map
100
+ ? BundleMetadata.fromJson(
101
+ (j['metadata'] as Map).cast<String, dynamic>())
102
+ : null,
103
+ manifestStorageUri:
104
+ _s<String>(j, 'manifestStorageUri', 'manifest_storage_uri'),
105
+ manifestFileHash:
106
+ _s<String>(j, 'manifestFileHash', 'manifest_file_hash'),
107
+ assetBaseStorageUri:
108
+ _s<String>(j, 'assetBaseStorageUri', 'asset_base_storage_uri'),
109
+ patches: (j['patches'] as List?)
110
+ ?.map((e) =>
111
+ BundlePatchArtifact.fromJson((e as Map).cast<String, dynamic>()))
112
+ .toList(),
113
+ patchBaseBundleId:
114
+ _s<String>(j, 'patchBaseBundleId', 'patch_base_bundle_id'),
115
+ patchBaseFileHash:
116
+ _s<String>(j, 'patchBaseFileHash', 'patch_base_file_hash'),
117
+ patchFileHash: _s(j, 'patchFileHash', 'patch_file_hash'),
118
+ patchStorageUri: _s<String>(j, 'patchStorageUri', 'patch_storage_uri'),
119
+ rolloutCohortCount:
120
+ _s<int>(j, 'rolloutCohortCount', 'rollout_cohort_count'),
121
+ targetCohorts:
122
+ (j['targetCohorts'] as List? ?? j['target_cohorts'] as List?)
123
+ ?.map((e) => e as String)
124
+ .toList(),
125
+ );
126
+
127
+ /// camelCase wire format (server API payloads).
128
+ Map<String, dynamic> toJson() => {
129
+ 'id': id,
130
+ 'platform': platform.value,
131
+ 'shouldForceUpdate': shouldForceUpdate,
132
+ 'enabled': enabled,
133
+ 'fileHash': fileHash,
134
+ 'storageUri': storageUri,
135
+ if (gitCommitHash != null) 'gitCommitHash': gitCommitHash,
136
+ if (message != null) 'message': message,
137
+ 'channel': channel,
138
+ if (targetAppVersion != null) 'targetAppVersion': targetAppVersion,
139
+ if (fingerprintHash != null) 'fingerprintHash': fingerprintHash,
140
+ if (metadata != null) 'metadata': metadata!.toJson(),
141
+ if (manifestStorageUri != null)
142
+ 'manifestStorageUri': manifestStorageUri,
143
+ if (manifestFileHash != null) 'manifestFileHash': manifestFileHash,
144
+ if (assetBaseStorageUri != null)
145
+ 'assetBaseStorageUri': assetBaseStorageUri,
146
+ if (patches != null)
147
+ 'patches': patches!.map((p) => p.toJson()).toList(),
148
+ if (patchBaseBundleId != null) 'patchBaseBundleId': patchBaseBundleId,
149
+ if (patchBaseFileHash != null)
150
+ 'patchBaseFileHash': patchBaseFileHash,
151
+ if (patchFileHashLegacy != null)
152
+ 'patchFileHash': patchFileHashLegacy,
153
+ if (patchStorageUri != null) 'patchStorageUri': patchStorageUri,
154
+ if (rolloutCohortCount != null)
155
+ 'rolloutCohortCount': rolloutCohortCount,
156
+ if (targetCohorts != null) 'targetCohorts': targetCohorts,
157
+ };
158
+
159
+ /// snake_case row format (Postgres/Supabase columns).
160
+ Map<String, dynamic> toSqlJson() => {
161
+ 'id': id,
162
+ 'platform': platform.value,
163
+ 'should_force_update': shouldForceUpdate,
164
+ 'enabled': enabled,
165
+ 'file_hash': fileHash,
166
+ 'storage_uri': storageUri,
167
+ 'git_commit_hash': gitCommitHash,
168
+ 'message': message,
169
+ 'channel': channel,
170
+ 'target_app_version': targetAppVersion,
171
+ 'fingerprint_hash': fingerprintHash,
172
+ 'metadata': metadata?.toJson() ?? <String, dynamic>{},
173
+ 'manifest_storage_uri': manifestStorageUri,
174
+ 'manifest_file_hash': manifestFileHash,
175
+ 'asset_base_storage_uri': assetBaseStorageUri,
176
+ 'patches': patches?.map((p) => p.toJson()).toList(),
177
+ 'patch_base_bundle_id': patchBaseBundleId,
178
+ 'patch_base_file_hash': patchBaseFileHash,
179
+ 'patch_file_hash': patchFileHashLegacy,
180
+ 'patch_storage_uri': patchStorageUri,
181
+ 'rollout_cohort_count': rolloutCohortCount,
182
+ 'target_cohorts': targetCohorts,
183
+ };
184
+
185
+ @override
186
+ bool operator ==(Object o) =>
187
+ o is Bundle && o.toJson().toString() == toJson().toString();
188
+
189
+ @override
190
+ int get hashCode => toJson().toString().hashCode;
191
+ }
@@ -0,0 +1,39 @@
1
+ /// Faithful port of hot-updater `packages/core/src/bundleArtifacts.ts`.
2
+ ///
3
+ /// Utility functions for reading and normalizing bundle patch metadata
4
+ /// from the flexible `Bundle` / `BundlePatchArtifact` shape.
5
+ library;
6
+
7
+ import 'bundle.dart';
8
+ import 'bundle_patch_artifact.dart';
9
+
10
+ /// Identity — returns the metadata as-is.
11
+ /// (TS version strips artifact-specific keys; Dart uses strong types so
12
+ /// there is nothing to strip.)
13
+ Map<String, Object?>? stripBundleArtifactMetadata(
14
+ Map<String, Object?>? metadata,
15
+ ) =>
16
+ metadata;
17
+
18
+ /// Extract `manifestStorageUri` from a bundle, defaulting to null.
19
+ String? getManifestStorageUri(Bundle bundle) => bundle.manifestStorageUri;
20
+
21
+ /// Extract `manifestFileHash` from a bundle, defaulting to null.
22
+ String? getManifestFileHash(Bundle bundle) => bundle.manifestFileHash;
23
+
24
+ /// Extract `assetBaseStorageUri` from a bundle, defaulting to null.
25
+ String? getAssetBaseStorageUri(Bundle bundle) => bundle.assetBaseStorageUri;
26
+
27
+ /// Deduplicate patches by [BundlePatchArtifact.baseBundleId], keeping the
28
+ /// first occurrence (by insertion order).
29
+ List<BundlePatchArtifact> getBundlePatches(Bundle bundle) {
30
+ final patches = bundle.patches;
31
+ if (patches == null || patches.isEmpty) return const [];
32
+
33
+ final seenBaseBundleIds = <String>{};
34
+ return patches.where((patch) {
35
+ if (seenBaseBundleIds.contains(patch.baseBundleId)) return false;
36
+ seenBaseBundleIds.add(patch.baseBundleId);
37
+ return true;
38
+ }).toList();
39
+ }
@@ -0,0 +1,47 @@
1
+ /// Binary patch artifact — hot-updater `BundlePatchArtifact`.
2
+ library;
3
+
4
+ class BundlePatchArtifact {
5
+ final String baseBundleId;
6
+ final String baseFileHash;
7
+ final String patchFileHash;
8
+ final String patchStorageUri;
9
+
10
+ const BundlePatchArtifact({
11
+ required this.baseBundleId,
12
+ required this.baseFileHash,
13
+ required this.patchFileHash,
14
+ required this.patchStorageUri,
15
+ });
16
+
17
+ factory BundlePatchArtifact.fromJson(Map<String, dynamic> j) =>
18
+ BundlePatchArtifact(
19
+ baseBundleId:
20
+ j['baseBundleId'] as String? ?? j['base_bundle_id'] as String,
21
+ baseFileHash:
22
+ j['baseFileHash'] as String? ?? j['base_file_hash'] as String,
23
+ patchFileHash:
24
+ j['patchFileHash'] as String? ?? j['patch_file_hash'] as String,
25
+ patchStorageUri: j['patchStorageUri'] as String? ??
26
+ j['patch_storage_uri'] as String,
27
+ );
28
+
29
+ Map<String, dynamic> toJson() => {
30
+ 'baseBundleId': baseBundleId,
31
+ 'baseFileHash': baseFileHash,
32
+ 'patchFileHash': patchFileHash,
33
+ 'patchStorageUri': patchStorageUri,
34
+ };
35
+
36
+ @override
37
+ bool operator ==(Object o) =>
38
+ o is BundlePatchArtifact &&
39
+ o.baseBundleId == baseBundleId &&
40
+ o.baseFileHash == baseFileHash &&
41
+ o.patchFileHash == patchFileHash &&
42
+ o.patchStorageUri == patchStorageUri;
43
+
44
+ @override
45
+ int get hashCode =>
46
+ Object.hash(baseBundleId, baseFileHash, patchFileHash, patchStorageUri);
47
+ }
@@ -0,0 +1,77 @@
1
+ /// Per-asset diff descriptors — hot-updater `ChangedAsset*`.
2
+ library;
3
+
4
+ class ChangedAssetFile {
5
+ /// "br" or null.
6
+ final String? compression;
7
+ final String url;
8
+
9
+ const ChangedAssetFile({required this.url, this.compression});
10
+
11
+ factory ChangedAssetFile.fromJson(Map<String, dynamic> j) =>
12
+ ChangedAssetFile(url: j['url'] as String, compression: j['compression'] as String?);
13
+
14
+ Map<String, dynamic> toJson() => {'url': url, 'compression': compression};
15
+ }
16
+
17
+ class ChangedAssetPatch {
18
+ final String baseBundleId;
19
+ final String baseFileHash;
20
+ final String patchFileHash;
21
+ final String patchUrl;
22
+
23
+ /// Always "bsdiff" in hot-updater; kept for wire compatibility.
24
+ final String algorithm;
25
+
26
+ const ChangedAssetPatch({
27
+ required this.baseBundleId,
28
+ required this.baseFileHash,
29
+ required this.patchFileHash,
30
+ required this.patchUrl,
31
+ this.algorithm = 'bsdiff',
32
+ });
33
+
34
+ factory ChangedAssetPatch.fromJson(Map<String, dynamic> j) =>
35
+ ChangedAssetPatch(
36
+ baseBundleId: j['baseBundleId'] as String,
37
+ baseFileHash: j['baseFileHash'] as String,
38
+ patchFileHash: j['patchFileHash'] as String,
39
+ patchUrl: j['patchUrl'] as String,
40
+ algorithm: j['algorithm'] as String? ?? 'bsdiff',
41
+ );
42
+
43
+ Map<String, dynamic> toJson() => {
44
+ 'algorithm': algorithm,
45
+ 'baseBundleId': baseBundleId,
46
+ 'baseFileHash': baseFileHash,
47
+ 'patchFileHash': patchFileHash,
48
+ 'patchUrl': patchUrl,
49
+ };
50
+ }
51
+
52
+ class ChangedAsset {
53
+ /// SHA256 hex of the asset file.
54
+ final String fileHash;
55
+ final ChangedAssetFile? file;
56
+ final ChangedAssetPatch? patch;
57
+
58
+ const ChangedAsset({required this.fileHash, this.file, this.patch});
59
+
60
+ factory ChangedAsset.fromJson(Map<String, dynamic> j) => ChangedAsset(
61
+ fileHash: j['fileHash'] as String,
62
+ file: j['file'] == null
63
+ ? null
64
+ : ChangedAssetFile.fromJson(
65
+ (j['file'] as Map).cast<String, dynamic>()),
66
+ patch: j['patch'] == null
67
+ ? null
68
+ : ChangedAssetPatch.fromJson(
69
+ (j['patch'] as Map).cast<String, dynamic>()),
70
+ );
71
+
72
+ Map<String, dynamic> toJson() => {
73
+ 'fileHash': fileHash,
74
+ 'file': file?.toJson(),
75
+ 'patch': patch?.toJson(),
76
+ };
77
+ }
@@ -0,0 +1,78 @@
1
+ import 'platform.dart';
2
+ import 'strategy.dart';
3
+ import 'uuid.dart' show nilUuid;
4
+
5
+ /// hot-updater `GetBundlesArgs` — update-check request parameters.
6
+ ///
7
+ /// Two variants keyed by [UpdateStrategy]; shared fields defaulted the same
8
+ /// way (minBundleId = NIL_UUID, channel = "production").
9
+ sealed class GetBundlesArgs {
10
+ final UpdateStrategy _updateStrategy;
11
+ final Platform platform;
12
+ final String bundleId;
13
+ final String minBundleId;
14
+ final String channel;
15
+
16
+ /// Cohort identifier for server-side rollout decisions.
17
+ final String? cohort;
18
+
19
+ const GetBundlesArgs({
20
+ required UpdateStrategy strategy,
21
+ required this.platform,
22
+ required this.bundleId,
23
+ this.minBundleId = nilUuid,
24
+ this.channel = 'production',
25
+ this.cohort,
26
+ }) : _updateStrategy = strategy;
27
+
28
+ /// Public accessor for the resolved update strategy.
29
+ UpdateStrategy get updateStrategy => _updateStrategy;
30
+
31
+ Map<String, dynamic> toJson() => {
32
+ '_updateStrategy': _updateStrategy.value,
33
+ 'platform': platform.value,
34
+ 'bundleId': bundleId,
35
+ if (minBundleId != nilUuid) 'minBundleId': minBundleId,
36
+ if (channel != 'production') 'channel': channel,
37
+ if (cohort != null) 'cohort': cohort,
38
+ };
39
+ }
40
+
41
+ class FingerprintGetBundlesArgs extends GetBundlesArgs {
42
+ final String fingerprintHash;
43
+
44
+ const FingerprintGetBundlesArgs({
45
+ required super.platform,
46
+ required super.bundleId,
47
+ super.minBundleId,
48
+ super.channel,
49
+ super.cohort,
50
+ required this.fingerprintHash,
51
+ }) : super(strategy: UpdateStrategy.fingerprint);
52
+
53
+ @override
54
+ Map<String, dynamic> toJson() => {
55
+ ...super.toJson(),
56
+ 'fingerprintHash': fingerprintHash,
57
+ };
58
+ }
59
+
60
+ class AppVersionGetBundlesArgs extends GetBundlesArgs {
61
+ /// Current app version (semver).
62
+ final String appVersion;
63
+
64
+ const AppVersionGetBundlesArgs({
65
+ required super.platform,
66
+ required super.bundleId,
67
+ super.minBundleId,
68
+ super.channel,
69
+ super.cohort,
70
+ required this.appVersion,
71
+ }) : super(strategy: UpdateStrategy.appVersion);
72
+
73
+ @override
74
+ Map<String, dynamic> toJson() => {
75
+ ...super.toJson(),
76
+ 'appVersion': appVersion,
77
+ };
78
+ }
@@ -0,0 +1,31 @@
1
+ /// Bundle metadata — hot-updater `BundleMetadata` (snake_case on the wire).
2
+ library;
3
+
4
+ class BundleMetadata {
5
+ final String? appVersion;
6
+
7
+ /// Ed25519 (base64) signature over the artifact's MD5 hex string. Stored here
8
+ /// (rather than a dedicated column) so it round-trips through every backend's
9
+ /// JSON `metadata` column without a schema migration. The device SDK reads
10
+ /// this to verify the patch (see [AppUpdateAvailableInfo.signature]).
11
+ final String? signature;
12
+
13
+ const BundleMetadata({this.appVersion, this.signature});
14
+
15
+ factory BundleMetadata.fromJson(Map<String, dynamic> j) => BundleMetadata(
16
+ appVersion: j['app_version'] as String?,
17
+ signature: j['signature'] as String?,
18
+ );
19
+
20
+ Map<String, dynamic> toJson() => {
21
+ 'app_version': appVersion,
22
+ if (signature != null) 'signature': signature,
23
+ };
24
+
25
+ @override
26
+ bool operator ==(Object o) =>
27
+ o is BundleMetadata && o.appVersion == appVersion && o.signature == signature;
28
+
29
+ @override
30
+ int get hashCode => Object.hash(appVersion, signature);
31
+ }