@_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.
- package/bin/flutter-patcher.js +48 -39
- package/dart-src/packages/cli-tools/.gitkeep +0 -0
- package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
- package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
- package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
- package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
- package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
- package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
- package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
- package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
- package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
- package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
- package/dart-src/packages/core/lib/src/bundle.dart +191 -0
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
- package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
- package/dart-src/packages/core/lib/src/metadata.dart +31 -0
- package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
- package/dart-src/packages/core/lib/src/platform.dart +15 -0
- package/dart-src/packages/core/lib/src/rollout.dart +209 -0
- package/dart-src/packages/core/lib/src/semver.dart +70 -0
- package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
- package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
- package/dart-src/packages/core/lib/src/status.dart +24 -0
- package/dart-src/packages/core/lib/src/strategy.dart +10 -0
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
- package/dart-src/packages/core/lib/src/uuid.dart +45 -0
- package/dart-src/packages/core/pubspec.yaml +9 -0
- package/dart-src/packages/core/tool/dbg.dart +6 -0
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
- package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
- package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
- package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
- package/dart-src/plugins/aws/pubspec.yaml +22 -0
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
- package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
- package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
- package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
- package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
- package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
- package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
- package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
- package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
- package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
- package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
- package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
- package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
- package/dart-src/plugins/postgres/pubspec.yaml +18 -0
- package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
- package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
- package/dart-src/plugins/standalone/.gitkeep +0 -0
- package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
- package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
- package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
- package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
- package/dart-src/plugins/standalone/pubspec.yaml +18 -0
- package/dart-src/plugins/supabase/lib/edge.dart +10 -0
- package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
- package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
- package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
- package/dart-src/plugins/supabase/pubspec.yaml +19 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +53 -25
- /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
|
+
}
|