@_nazmiforreal/flutter-ota 0.1.11 → 0.1.13
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/README.md +1 -1
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
- package/dart-src/packages/cli-tools/README.md +1 -1
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
- package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +299 -190
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
- package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
- package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
- package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
- package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
- package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
- package/dart-src/packages/cli-tools/pubspec.lock +69 -116
- package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
- package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
- package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
- package/dart-src/packages/core/README.md +2 -2
- package/dart-src/packages/core/example/lib/main.dart +69 -0
- package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
- package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
- package/dart-src/packages/core/lib/src/bundle.dart +102 -94
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
- package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
- package/dart-src/packages/core/lib/src/metadata.dart +9 -7
- package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
- package/dart-src/packages/core/lib/src/platform.dart +4 -3
- package/dart-src/packages/core/lib/src/rollout.dart +14 -11
- package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
- package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
- package/dart-src/packages/core/lib/src/status.dart +4 -3
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
- package/dart-src/packages/core/lib/src/uuid.dart +1 -2
- package/dart-src/packages/core/pubspec.yaml +6 -7
- package/dart-src/packages/core/test/rollout_test.dart +15 -8
- package/dart-src/packages/core/test/semver_test.dart +5 -2
- package/dart-src/packages/core/test/types_test.dart +26 -24
- package/dart-src/packages/core/test/uuid_test.dart +3 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -0
- package/dart-src/plugins/aws/CHANGELOG.md +6 -0
- package/dart-src/plugins/aws/example/lib/main.dart +60 -0
- package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
- package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
- package/dart-src/plugins/aws/pubspec.yaml +15 -10
- package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
- package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
- package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
- package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
- package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
- package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
- package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
- package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
- package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
- package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
- package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
- package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
- package/dart-src/plugins/pocketbase/LICENSE +21 -0
- package/dart-src/plugins/pocketbase/README.md +54 -0
- package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
- package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
- package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
- package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
- package/dart-src/plugins/postgres/pubspec.yaml +14 -9
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
- package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
- package/dart-src/plugins/supabase/README.md +2 -2
- package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
- package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
- package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
- package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
- package/dart-src/plugins/supabase/pubspec.yaml +14 -9
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
- package/package.json +2 -2
- package/scripts/vendor.sh +1 -1
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import 'dart:convert' show jsonDecode;
|
|
2
2
|
|
|
3
3
|
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
|
|
4
|
-
show
|
|
4
|
+
show
|
|
5
|
+
Bundle,
|
|
6
|
+
BundleMetadata,
|
|
7
|
+
BundlePatchArtifact,
|
|
8
|
+
Platform,
|
|
9
|
+
getBundlePatches;
|
|
10
|
+
|
|
11
|
+
import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
12
|
+
show buildBundlePatchId, normalizeMetadata;
|
|
13
|
+
// Re-export the shared `buildBundlePatchId` so the cloudflare package's public
|
|
14
|
+
// API still exposes it; `parseMetadata` remains a backward-compatible alias of
|
|
15
|
+
// the shared `normalizeMetadata`.
|
|
16
|
+
export 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
17
|
+
show buildBundlePatchId;
|
|
5
18
|
|
|
6
19
|
/// Default rollout cohort count — full rollout in the Dart port's per-mille
|
|
7
20
|
/// scale (matches core's `defaultRolloutCohortCount` = `numericCohortSize`).
|
|
8
21
|
const int defaultRolloutCohortCount = 1000;
|
|
9
22
|
|
|
10
|
-
/// Compound patch id `${bundleId}:${baseBundleId}` (matches D1 schema).
|
|
11
|
-
String buildBundlePatchId(String bundleId, String baseBundleId) =>
|
|
12
|
-
'$bundleId:$baseBundleId';
|
|
13
|
-
|
|
14
23
|
/// Parse `target_cohorts` which is stored as a JSON string (or already a list).
|
|
15
24
|
List<String>? parseTargetCohorts(dynamic value) {
|
|
16
25
|
if (value == null) return null;
|
|
@@ -36,21 +45,8 @@ List<String>? parseTargetCohorts(dynamic value) {
|
|
|
36
45
|
return null;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
|
-
///
|
|
40
|
-
Map<String, Object?>? parseMetadata(dynamic value)
|
|
41
|
-
if (value == null) return null;
|
|
42
|
-
if (value is String) {
|
|
43
|
-
try {
|
|
44
|
-
return parseMetadata(jsonDecode(value));
|
|
45
|
-
} catch (_) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (value is Map) {
|
|
50
|
-
return value.cast<String, Object?>();
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
48
|
+
/// Backward-compatible alias for [normalizeMetadata].
|
|
49
|
+
Map<String, Object?>? parseMetadata(dynamic value) => normalizeMetadata(value);
|
|
54
50
|
|
|
55
51
|
/// Convert a [Bundle] into the rows inserted into `bundle_patches`.
|
|
56
52
|
List<Map<String, dynamic>> bundleToPatchRows(Bundle bundle) =>
|
|
@@ -72,18 +68,19 @@ Bundle transformRowToBundle(
|
|
|
72
68
|
Map<String, dynamic> row, [
|
|
73
69
|
List<Map<String, dynamic>> patchRows = const [],
|
|
74
70
|
]) {
|
|
75
|
-
final rawMetadata =
|
|
71
|
+
final rawMetadata = normalizeMetadata(row['metadata']);
|
|
76
72
|
final metadata = rawMetadata == null
|
|
77
73
|
? null
|
|
78
74
|
: BundleMetadata.fromJson(rawMetadata.cast<String, dynamic>());
|
|
79
|
-
final patches = patchRows
|
|
80
|
-
.toList()
|
|
75
|
+
final patches = patchRows.toList()
|
|
81
76
|
..sort(
|
|
82
77
|
(left, right) =>
|
|
83
|
-
(left['order_index'] as num? ?? 0)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
(left['order_index'] as num? ?? 0).compareTo(
|
|
79
|
+
right['order_index'] as num? ?? 0,
|
|
80
|
+
) +
|
|
81
|
+
(left['base_bundle_id'] as String? ?? '').compareTo(
|
|
82
|
+
right['base_bundle_id'] as String? ?? '',
|
|
83
|
+
),
|
|
87
84
|
);
|
|
88
85
|
|
|
89
86
|
final bundlePatches = patches.map((patch) {
|
|
@@ -24,10 +24,7 @@ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
|
24
24
|
ResolveUpdateInfoFromBundlesOptions;
|
|
25
25
|
|
|
26
26
|
import 'd1_bundle_mapper.dart'
|
|
27
|
-
show
|
|
28
|
-
bundleToPatchRows,
|
|
29
|
-
defaultRolloutCohortCount,
|
|
30
|
-
transformRowToBundle;
|
|
27
|
+
show bundleToPatchRows, defaultRolloutCohortCount, transformRowToBundle;
|
|
31
28
|
import 'd1_build_where.dart' show buildWhereClause;
|
|
32
29
|
import 'd1_client.dart' show D1ClientLike;
|
|
33
30
|
|
|
@@ -77,8 +74,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
77
74
|
DatabaseBundleQueryOrder? orderBy,
|
|
78
75
|
) async {
|
|
79
76
|
final where = buildWhereClause(conditions);
|
|
80
|
-
final orderBySql =
|
|
81
|
-
|
|
77
|
+
final orderBySql = orderBy?.direction == 'asc'
|
|
78
|
+
? 'ORDER BY id ASC'
|
|
79
|
+
: 'ORDER BY id DESC';
|
|
82
80
|
|
|
83
81
|
final rows = await _client.query(
|
|
84
82
|
'SELECT * FROM bundles ${where.sql} $orderBySql LIMIT ? OFFSET ?',
|
|
@@ -89,7 +87,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
89
87
|
rows.map((row) => row['id'] as String).toList(),
|
|
90
88
|
);
|
|
91
89
|
return rows
|
|
92
|
-
.map(
|
|
90
|
+
.map(
|
|
91
|
+
(row) => transformRowToBundle(row, patchMap[row['id']] ?? const []),
|
|
92
|
+
)
|
|
93
93
|
.toList();
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -105,7 +105,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
105
105
|
rows.map((row) => row['id'] as String).toList(),
|
|
106
106
|
);
|
|
107
107
|
return rows
|
|
108
|
-
.map(
|
|
108
|
+
.map(
|
|
109
|
+
(row) => transformRowToBundle(row, patchMap[row['id']] ?? const []),
|
|
110
|
+
)
|
|
109
111
|
.toList();
|
|
110
112
|
}
|
|
111
113
|
|
|
@@ -121,9 +123,7 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
121
123
|
'GROUP BY target_app_version',
|
|
122
124
|
[channel, platform, minBundleId],
|
|
123
125
|
);
|
|
124
|
-
return rows
|
|
125
|
-
.map((row) => row['target_app_version'] as String)
|
|
126
|
-
.toList();
|
|
126
|
+
return rows.map((row) => row['target_app_version'] as String).toList();
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
@override
|
|
@@ -197,7 +197,8 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
197
197
|
) async {
|
|
198
198
|
final where = options.where ?? const DatabaseBundleQueryWhere();
|
|
199
199
|
final limit = options.limit;
|
|
200
|
-
final offset =
|
|
200
|
+
final offset =
|
|
201
|
+
options.offset ??
|
|
201
202
|
(options.page != null ? (options.page! - 1) * limit : 0);
|
|
202
203
|
|
|
203
204
|
final totalCount = await _getTotalCount(where);
|
|
@@ -232,25 +233,19 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
232
233
|
Future<void> onUnmount() async {}
|
|
233
234
|
|
|
234
235
|
@override
|
|
235
|
-
Future<void> commitBundle({
|
|
236
|
-
required List<BundleChange> changedSets,
|
|
237
|
-
}) async {
|
|
236
|
+
Future<void> commitBundle({required List<BundleChange> changedSets}) async {
|
|
238
237
|
if (changedSets.isEmpty) return;
|
|
239
238
|
|
|
240
239
|
for (final op in changedSets) {
|
|
241
240
|
if (op.operation == BundleChangeOperation.delete) {
|
|
242
|
-
await _client.query(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
241
|
+
await _client.query('DELETE FROM bundle_patches WHERE bundle_id = ?', [
|
|
242
|
+
op.data.id,
|
|
243
|
+
]);
|
|
246
244
|
await _client.query(
|
|
247
245
|
'DELETE FROM bundle_patches WHERE base_bundle_id = ?',
|
|
248
246
|
[op.data.id],
|
|
249
247
|
);
|
|
250
|
-
await _client.query(
|
|
251
|
-
'DELETE FROM bundles WHERE id = ?',
|
|
252
|
-
[op.data.id],
|
|
253
|
-
);
|
|
248
|
+
await _client.query('DELETE FROM bundles WHERE id = ?', [op.data.id]);
|
|
254
249
|
} else {
|
|
255
250
|
final bundle = op.data;
|
|
256
251
|
await _client.query(
|
|
@@ -272,9 +267,7 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
272
267
|
bundle.targetAppVersion,
|
|
273
268
|
bundle.storageUri,
|
|
274
269
|
bundle.fingerprintHash,
|
|
275
|
-
jsonEncode(
|
|
276
|
-
bundle.metadata?.toJson() ?? <String, dynamic>{},
|
|
277
|
-
),
|
|
270
|
+
jsonEncode(bundle.metadata?.toJson() ?? <String, dynamic>{}),
|
|
278
271
|
bundle.manifestStorageUri,
|
|
279
272
|
bundle.manifestFileHash,
|
|
280
273
|
bundle.assetBaseStorageUri,
|
|
@@ -285,10 +278,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
|
|
|
285
278
|
],
|
|
286
279
|
);
|
|
287
280
|
|
|
288
|
-
await _client.query(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
);
|
|
281
|
+
await _client.query('DELETE FROM bundle_patches WHERE bundle_id = ?', [
|
|
282
|
+
bundle.id,
|
|
283
|
+
]);
|
|
292
284
|
|
|
293
285
|
final patchRows = bundleToPatchRows(bundle);
|
|
294
286
|
for (final patchRow in patchRows) {
|
|
@@ -35,7 +35,7 @@ class R2S3StorageConfig {
|
|
|
35
35
|
/// R2 region (defaults to "auto").
|
|
36
36
|
final String region;
|
|
37
37
|
|
|
38
|
-
/// Override endpoint (defaults to `https
|
|
38
|
+
/// Override endpoint (defaults to `https://{accountId}.r2.cloudflarestorage.com`).
|
|
39
39
|
final String? endpoint;
|
|
40
40
|
|
|
41
41
|
/// Optional factory used to inject an [R2S3ClientLike] in tests.
|
|
@@ -44,7 +44,8 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
44
44
|
static const String _service = 'r2';
|
|
45
45
|
|
|
46
46
|
Uri _objectUri(String key) {
|
|
47
|
-
final host =
|
|
47
|
+
final host =
|
|
48
|
+
config.endpoint ??
|
|
48
49
|
'https://${config.accountId}.r2.cloudflarestorage.com';
|
|
49
50
|
return Uri.parse('$host/${config.bucketName}/$key');
|
|
50
51
|
}
|
|
@@ -56,11 +57,7 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
@override
|
|
59
|
-
Future<void> putObject(
|
|
60
|
-
String key,
|
|
61
|
-
List<int> body,
|
|
62
|
-
String contentType,
|
|
63
|
-
) async {
|
|
60
|
+
Future<void> putObject(String key, List<int> body, String contentType) async {
|
|
64
61
|
final uri = _objectUri(key);
|
|
65
62
|
final response = await _send(
|
|
66
63
|
'PUT',
|
|
@@ -77,7 +74,8 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
77
74
|
@override
|
|
78
75
|
Future<bool> headObject(String key) async {
|
|
79
76
|
final uri = _objectUri(key);
|
|
80
|
-
final request = Request('HEAD', uri)
|
|
77
|
+
final request = Request('HEAD', uri)
|
|
78
|
+
..headers.addAll(_authHeaders('HEAD', uri, null));
|
|
81
79
|
final response = await _http.send(request);
|
|
82
80
|
if (response.statusCode == 404) return false;
|
|
83
81
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
@@ -114,21 +112,18 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
114
112
|
'X-Amz-Expires': '$expiresIn',
|
|
115
113
|
'X-Amz-SignedHeaders': 'host',
|
|
116
114
|
};
|
|
117
|
-
final canonicalQuery =
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
final canonicalQuery =
|
|
116
|
+
params.entries
|
|
117
|
+
.map((e) => '${_uriEncode(e.key)}=${_uriEncode(e.value)}')
|
|
118
|
+
.toList()
|
|
119
|
+
..sort();
|
|
121
120
|
final queryString = canonicalQuery.join('&');
|
|
122
121
|
|
|
123
122
|
const payloadHash = 'UNSIGNED-PAYLOAD';
|
|
124
123
|
final canonicalHeaders = 'host:${uri.host}\n';
|
|
125
124
|
final canonicalRequest =
|
|
126
125
|
'GET\n${uri.path}\n$queryString\n$canonicalHeaders\nhost\n$payloadHash';
|
|
127
|
-
final stringToSign = _stringToSign(
|
|
128
|
-
amzDate,
|
|
129
|
-
scope,
|
|
130
|
-
canonicalRequest,
|
|
131
|
-
);
|
|
126
|
+
final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
|
|
132
127
|
final signingKey = _signingKey(dateStamp, config.region);
|
|
133
128
|
final signature = _toHex(_hmac(signingKey, stringToSign));
|
|
134
129
|
return '${uri.scheme}://${uri.host}${uri.path}?$queryString'
|
|
@@ -158,23 +153,21 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
158
153
|
|
|
159
154
|
// --- signing helpers -------------------------------------------------------
|
|
160
155
|
|
|
161
|
-
Map<String, String> _authHeaders(
|
|
162
|
-
String method,
|
|
163
|
-
Uri uri,
|
|
164
|
-
List<int>? body,
|
|
165
|
-
) {
|
|
156
|
+
Map<String, String> _authHeaders(String method, Uri uri, List<int>? body) {
|
|
166
157
|
final now = DateTime.now().toUtc();
|
|
167
158
|
final amzDate = _amzDate(now);
|
|
168
159
|
final dateStamp = amzDate.substring(0, 8);
|
|
169
160
|
final scope = '$dateStamp/${config.region}/$_service/aws4_request';
|
|
170
161
|
final payloadHash = body == null ? _hashHex(const []) : _hashHex(body);
|
|
171
162
|
|
|
172
|
-
final canonicalHeaders =
|
|
163
|
+
final canonicalHeaders =
|
|
164
|
+
'host:${uri.host}\n'
|
|
173
165
|
'x-amz-content-sha256:$payloadHash\n'
|
|
174
166
|
'x-amz-date:$amzDate\n';
|
|
175
167
|
const signedHeaders = 'host;x-amz-content-sha256;x-amz-date';
|
|
176
168
|
|
|
177
|
-
final canonicalRequest =
|
|
169
|
+
final canonicalRequest =
|
|
170
|
+
'$method\n${uri.path}\n'
|
|
178
171
|
'\n$canonicalHeaders$signedHeaders\n$payloadHash';
|
|
179
172
|
final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
|
|
180
173
|
final signingKey = _signingKey(dateStamp, config.region);
|
|
@@ -205,7 +198,10 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
205
198
|
|
|
206
199
|
List<StorageObject> _parseListObjects(String xml) {
|
|
207
200
|
final objects = <StorageObject>[];
|
|
208
|
-
final contents = _allMatches(
|
|
201
|
+
final contents = _allMatches(
|
|
202
|
+
xml,
|
|
203
|
+
RegExp(r'<Contents>([\s\S]*?)</Contents>'),
|
|
204
|
+
);
|
|
209
205
|
for (final block in contents) {
|
|
210
206
|
final key = _tag(block, 'Key');
|
|
211
207
|
final size = int.tryParse(_tag(block, 'Size') ?? '');
|
|
@@ -262,11 +258,7 @@ class R2S3Client implements R2S3ClientLike {
|
|
|
262
258
|
return m?.group(1);
|
|
263
259
|
}
|
|
264
260
|
|
|
265
|
-
static void _assertStatus(
|
|
266
|
-
Response response,
|
|
267
|
-
List<int> ok,
|
|
268
|
-
String operation,
|
|
269
|
-
) {
|
|
261
|
+
static void _assertStatus(Response response, List<int> ok, String operation) {
|
|
270
262
|
if (!ok.contains(response.statusCode)) {
|
|
271
263
|
throw Exception(
|
|
272
264
|
'R2 $operation failed (${response.statusCode}): ${response.body}',
|
|
@@ -14,10 +14,10 @@ import 'r2_storage_profile.dart'
|
|
|
14
14
|
/// ported here.
|
|
15
15
|
final StoragePlugin Function(R2S3StorageConfig config) r2Storage =
|
|
16
16
|
createUniversalStoragePlugin<R2S3StorageConfig>(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
);
|
|
17
|
+
name: 'r2Storage',
|
|
18
|
+
supportedProtocol: 'r2',
|
|
19
|
+
factory: (config) => (
|
|
20
|
+
node: createS3StorageProfile(config),
|
|
21
|
+
runtime: createS3RuntimeStorageProfile(config),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
@@ -6,20 +6,13 @@ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
|
6
6
|
RuntimeStorageProfile,
|
|
7
7
|
StorageObject,
|
|
8
8
|
createStorageKeyBuilder,
|
|
9
|
+
ensureExpectedBucket,
|
|
9
10
|
getContentType,
|
|
10
11
|
parseStorageUri;
|
|
11
12
|
|
|
12
13
|
import 'r2_config.dart' show R2S3StorageConfig, resolveR2Client;
|
|
13
14
|
import 'r2_s3_client.dart' show R2S3ClientLike, readFileBytes, writeFileBytes;
|
|
14
15
|
|
|
15
|
-
void _ensureExpectedBucket(String bucket, String bucketName) {
|
|
16
|
-
if (bucket != bucketName) {
|
|
17
|
-
throw ArgumentError(
|
|
18
|
-
'Bucket name mismatch: expected "$bucketName", but found "$bucket".',
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
16
|
/// Build the node (deploy/CLI) storage profile for R2 S3.
|
|
24
17
|
NodeStorageProfile createS3StorageProfile(R2S3StorageConfig config) {
|
|
25
18
|
final R2S3ClientLike client = resolveR2Client(config);
|
|
@@ -55,21 +48,21 @@ class _R2NodeProfile implements NodeStorageProfile {
|
|
|
55
48
|
@override
|
|
56
49
|
Future<bool> exists(String storageUri) async {
|
|
57
50
|
final parsed = parseStorageUri(storageUri, 'r2');
|
|
58
|
-
|
|
51
|
+
ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
59
52
|
return _client.headObject(parsed.key);
|
|
60
53
|
}
|
|
61
54
|
|
|
62
55
|
@override
|
|
63
56
|
Future<void> delete(String storageUri) async {
|
|
64
57
|
final parsed = parseStorageUri(storageUri, 'r2');
|
|
65
|
-
|
|
58
|
+
ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
66
59
|
await _client.deleteObject(parsed.key);
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
@override
|
|
70
63
|
Future<void> downloadFile(String storageUri, String filePath) async {
|
|
71
64
|
final parsed = parseStorageUri(storageUri, 'r2');
|
|
72
|
-
|
|
65
|
+
ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
73
66
|
final bytes = await _client.getObjectAsBytes(parsed.key);
|
|
74
67
|
await writeFileBytes(filePath, bytes);
|
|
75
68
|
}
|
|
@@ -79,8 +72,7 @@ class _R2NodeProfile implements NodeStorageProfile {
|
|
|
79
72
|
_client.listObjects(prefix);
|
|
80
73
|
|
|
81
74
|
@override
|
|
82
|
-
Future<void> deleteObjects(List<String> keys) =>
|
|
83
|
-
_client.deleteObjects(keys);
|
|
75
|
+
Future<void> deleteObjects(List<String> keys) => _client.deleteObjects(keys);
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
class _R2RuntimeProfile implements RuntimeStorageProfile {
|
|
@@ -92,7 +84,7 @@ class _R2RuntimeProfile implements RuntimeStorageProfile {
|
|
|
92
84
|
@override
|
|
93
85
|
Future<Map<String, String>> getDownloadUrl(String storageUri) async {
|
|
94
86
|
final parsed = parseStorageUri(storageUri, 'r2');
|
|
95
|
-
|
|
87
|
+
ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
96
88
|
final fileUrl = await _client.getSignedUrl(parsed.key);
|
|
97
89
|
return {'fileUrl': fileUrl};
|
|
98
90
|
}
|
|
@@ -100,7 +92,7 @@ class _R2RuntimeProfile implements RuntimeStorageProfile {
|
|
|
100
92
|
@override
|
|
101
93
|
Future<String?> readText(String storageUri) async {
|
|
102
94
|
final parsed = parseStorageUri(storageUri, 'r2');
|
|
103
|
-
|
|
95
|
+
ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
104
96
|
try {
|
|
105
97
|
return await _client.getObjectAsString(parsed.key);
|
|
106
98
|
} catch (_) {
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
name: flutter_ota_kit_cloudflare
|
|
2
2
|
description: Cloudflare D1 + R2 storage plugins for flutter_ota_kit (faithful port of hot-updater plugins/cloudflare).
|
|
3
|
-
version: 0.1.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
documentation: https://github.com/HYPER12755/flutter_ota_kit#readme
|
|
3
|
+
version: 0.1.2
|
|
4
|
+
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
|
+
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
|
+
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
7
|
+
documentation: https://github.com/HYPER12755/flutter-ota-kit#readme
|
|
9
8
|
license: MIT
|
|
10
9
|
|
|
11
10
|
environment:
|
|
12
|
-
sdk: ">=3.
|
|
11
|
+
sdk: ">=3.13.2 <4.0.0"
|
|
13
12
|
|
|
14
13
|
dependencies:
|
|
15
|
-
flutter_ota_kit_core: ^0.1.
|
|
16
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.1
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.3
|
|
17
16
|
crypto: ^3.0.0
|
|
18
17
|
http: ^1.2.0
|
|
19
18
|
path: ^1.9.0
|
|
@@ -21,3 +20,9 @@ dependencies:
|
|
|
21
20
|
dev_dependencies:
|
|
22
21
|
test: ^1.25.0
|
|
23
22
|
lints: ^4.0.0
|
|
23
|
+
|
|
24
|
+
dependency_overrides:
|
|
25
|
+
flutter_ota_kit_core:
|
|
26
|
+
path: ../../packages/core
|
|
27
|
+
flutter_ota_kit_plugin_core:
|
|
28
|
+
path: ../plugin-core
|
|
@@ -21,20 +21,19 @@ Bundle _makeBundle({
|
|
|
21
21
|
String? targetAppVersion,
|
|
22
22
|
String? message,
|
|
23
23
|
List<BundlePatchArtifact>? patches,
|
|
24
|
-
}) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
24
|
+
}) => Bundle(
|
|
25
|
+
id: id,
|
|
26
|
+
channel: channel,
|
|
27
|
+
enabled: enabled,
|
|
28
|
+
shouldForceUpdate: false,
|
|
29
|
+
fileHash: 'h-$id',
|
|
30
|
+
platform: Platform.android,
|
|
31
|
+
targetAppVersion: targetAppVersion,
|
|
32
|
+
storageUri: 's3://$id',
|
|
33
|
+
fingerprintHash: fingerprintHash,
|
|
34
|
+
message: message,
|
|
35
|
+
patches: patches,
|
|
36
|
+
);
|
|
38
37
|
|
|
39
38
|
void main() {
|
|
40
39
|
group('cloudflare d1WorkerDatabase', () {
|
|
@@ -57,19 +56,20 @@ void main() {
|
|
|
57
56
|
expect(channels, containsAll(['production', 'staging']));
|
|
58
57
|
});
|
|
59
58
|
|
|
60
|
-
test(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
test(
|
|
60
|
+
'getBundleById returns null for missing, bundle for existing',
|
|
61
|
+
() async {
|
|
62
|
+
await plugin.appendBundle(_makeBundle(id: 'b1'));
|
|
63
|
+
await plugin.commitBundle();
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
expect(await plugin.getBundleById('missing'), isNull);
|
|
66
|
+
final got = await plugin.getBundleById('b1');
|
|
67
|
+
expect(got, isNotNull);
|
|
68
|
+
expect(got!.id, 'b1');
|
|
69
|
+
},
|
|
70
|
+
);
|
|
70
71
|
|
|
71
|
-
test('commitBundle append -> update -> delete reflects in store',
|
|
72
|
-
() async {
|
|
72
|
+
test('commitBundle append -> update -> delete reflects in store', () async {
|
|
73
73
|
await plugin.appendBundle(_makeBundle(id: 'b1', message: 'original'));
|
|
74
74
|
await plugin.commitBundle();
|
|
75
75
|
expect((await plugin.getBundleById('b1'))!.message, 'original');
|
|
@@ -85,24 +85,28 @@ void main() {
|
|
|
85
85
|
expect(await plugin.getBundleById('b1'), isNull);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
test(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
test(
|
|
89
|
+
'getUpdateInfo (appVersion) returns UpdateInfo for compatible bundle',
|
|
90
|
+
() async {
|
|
91
|
+
await plugin.appendBundle(
|
|
92
|
+
_makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
|
|
93
|
+
);
|
|
94
|
+
await plugin.commitBundle();
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
final info = await plugin.getUpdateInfo(
|
|
97
|
+
AppVersionGetBundlesArgs(
|
|
98
|
+
platform: Platform.android,
|
|
99
|
+
bundleId: nilUuid,
|
|
100
|
+
appVersion: '1.0.0',
|
|
101
|
+
channel: 'production',
|
|
102
|
+
minBundleId: nilUuid,
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
expect(info, isNotNull);
|
|
106
|
+
expect(info!.id, 'b1');
|
|
107
|
+
expect(info.status, UpdateStatus.update);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
106
110
|
|
|
107
111
|
test('getUpdateInfo (fingerprint) returns matching bundle', () async {
|
|
108
112
|
await plugin.appendBundle(_makeBundle(id: 'fp', fingerprintHash: 'fp1'));
|