@_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,298 @@
|
|
|
1
|
+
/// Faithful port of hot-updater `plugins/supabase/src/supabaseStorage.ts`.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'dart:io' as io;
|
|
5
|
+
|
|
6
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
|
|
7
|
+
|
|
8
|
+
import 'supabase_client_adapter.dart';
|
|
9
|
+
import 'supabase_client_http.dart';
|
|
10
|
+
import 'supabase_config.dart';
|
|
11
|
+
import 'supabase_signed_url_batcher.dart';
|
|
12
|
+
|
|
13
|
+
/// Parse a `supabase-storage://` URI into bucket + key.
|
|
14
|
+
ParsedStorageUri parseSupabaseStorageUri(String storageUri) =>
|
|
15
|
+
parseStorageUri(storageUri, 'supabase-storage');
|
|
16
|
+
|
|
17
|
+
/// Configuration for the Supabase storage plugin.
|
|
18
|
+
class SupabaseStorageConfig extends SupabaseServiceRoleConfig {
|
|
19
|
+
final String bucketName;
|
|
20
|
+
|
|
21
|
+
/// Base path where bundles are stored in the bucket.
|
|
22
|
+
final String? basePath;
|
|
23
|
+
|
|
24
|
+
const SupabaseStorageConfig({
|
|
25
|
+
required super.supabaseUrl,
|
|
26
|
+
super.supabaseServiceRoleKey,
|
|
27
|
+
super.supabaseAnonKey,
|
|
28
|
+
super.clientFactory,
|
|
29
|
+
required this.bucketName,
|
|
30
|
+
this.basePath,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
String _getErrorMessage(Object? error) {
|
|
35
|
+
if (error is Error) return error.toString();
|
|
36
|
+
if (error is Exception) return error.toString();
|
|
37
|
+
if (error is Map && error['message'] is String) {
|
|
38
|
+
return error['message'] as String;
|
|
39
|
+
}
|
|
40
|
+
return error.toString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Future<String> _createSignedUrlOrThrow({
|
|
44
|
+
required SupabaseStorageBucketLike bucket,
|
|
45
|
+
required String key,
|
|
46
|
+
required int expiresIn,
|
|
47
|
+
}) async {
|
|
48
|
+
late Object? error;
|
|
49
|
+
String? signedUrl;
|
|
50
|
+
try {
|
|
51
|
+
final response = await bucket.createSignedUrl(key, expiresIn);
|
|
52
|
+
signedUrl = response.signedUrl;
|
|
53
|
+
error = response.error;
|
|
54
|
+
} catch (thrown) {
|
|
55
|
+
error = thrown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (error == null && signedUrl != null) {
|
|
59
|
+
return signedUrl;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw StateError(
|
|
63
|
+
'Failed to generate download URL for "$key": '
|
|
64
|
+
'${_getErrorMessage(error ?? StateError("missing signed URL"))}',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Future<void> _verifyObjectCanBeSignedForRuntime({
|
|
69
|
+
required SupabaseStorageBucketLike bucket,
|
|
70
|
+
required String key,
|
|
71
|
+
}) async {
|
|
72
|
+
await _createSignedUrlOrThrow(
|
|
73
|
+
bucket: bucket,
|
|
74
|
+
key: key,
|
|
75
|
+
expiresIn: 3600,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Supabase-backed universal (node + runtime) storage plugin.
|
|
80
|
+
final supabaseStorage = createUniversalStoragePlugin<SupabaseStorageConfig>(
|
|
81
|
+
name: 'supabaseStorage',
|
|
82
|
+
supportedProtocol: 'supabase-storage',
|
|
83
|
+
factory: (config) {
|
|
84
|
+
final supabase = config.clientFactory != null
|
|
85
|
+
? config.clientFactory!(config.supabaseUrl, config.resolveKey())
|
|
86
|
+
: createSupabaseHttpClient(config.supabaseUrl, config.resolveKey());
|
|
87
|
+
|
|
88
|
+
final bucket = supabase.storage.from(config.bucketName);
|
|
89
|
+
final getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
90
|
+
final resolveSignedUrl = createSupabaseSignedUrlBatcher(
|
|
91
|
+
createSignedUrls: (bucketName, keys, expiresIn) =>
|
|
92
|
+
supabase.storage.from(bucketName).createSignedUrls(keys, expiresIn),
|
|
93
|
+
expiresIn: 3600,
|
|
94
|
+
formatObjectPath: (bucketName, key) => key,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
node: NodeStorageProfileImpl(
|
|
99
|
+
bucket: bucket,
|
|
100
|
+
getStorageKey: getStorageKey,
|
|
101
|
+
bucketName: config.bucketName,
|
|
102
|
+
verifySigned: _verifyObjectCanBeSignedForRuntime,
|
|
103
|
+
createSignedUrlOrThrow: _createSignedUrlOrThrow,
|
|
104
|
+
),
|
|
105
|
+
runtime: RuntimeStorageProfileImpl(
|
|
106
|
+
bucket: bucket,
|
|
107
|
+
resolveSignedUrl: resolveSignedUrl,
|
|
108
|
+
bucketName: config.bucketName,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
class NodeStorageProfileImpl implements NodeStorageProfile {
|
|
115
|
+
NodeStorageProfileImpl({
|
|
116
|
+
required this.bucket,
|
|
117
|
+
required this.getStorageKey,
|
|
118
|
+
required this.bucketName,
|
|
119
|
+
required this.verifySigned,
|
|
120
|
+
required this.createSignedUrlOrThrow,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
final SupabaseStorageBucketLike bucket;
|
|
124
|
+
final String Function(String, [String, String, String]) getStorageKey;
|
|
125
|
+
final String bucketName;
|
|
126
|
+
final Future<void> Function({
|
|
127
|
+
required SupabaseStorageBucketLike bucket,
|
|
128
|
+
required String key,
|
|
129
|
+
}) verifySigned;
|
|
130
|
+
final Future<String> Function({
|
|
131
|
+
required SupabaseStorageBucketLike bucket,
|
|
132
|
+
required String key,
|
|
133
|
+
required int expiresIn,
|
|
134
|
+
}) createSignedUrlOrThrow;
|
|
135
|
+
|
|
136
|
+
@override
|
|
137
|
+
Future<Map<String, String>> upload(String key, String filePath) async {
|
|
138
|
+
final body = await io.File(filePath).readAsBytes();
|
|
139
|
+
final contentType = getContentType(filePath);
|
|
140
|
+
final filename = filePath.split(io.Platform.pathSeparator).last;
|
|
141
|
+
final storageKey = getStorageKey(key, filename);
|
|
142
|
+
|
|
143
|
+
final upload = await bucket.upload(
|
|
144
|
+
storageKey,
|
|
145
|
+
body,
|
|
146
|
+
contentType: contentType,
|
|
147
|
+
cacheControl: 'max-age=31536000',
|
|
148
|
+
);
|
|
149
|
+
if (upload.error != null) {
|
|
150
|
+
throw StateError(_getErrorMessage(upload.error));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await verifySigned(bucket: bucket, key: storageKey);
|
|
154
|
+
final fullPath = upload.data is Map
|
|
155
|
+
? (upload.data as Map)['fullPath'] as String?
|
|
156
|
+
: null;
|
|
157
|
+
return {
|
|
158
|
+
'storageUri': 'supabase-storage://${fullPath ?? storageKey}',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
Future<bool> exists(String storageUri) async {
|
|
164
|
+
final parsed = parseSupabaseStorageUri(storageUri);
|
|
165
|
+
if (parsed.bucket != bucketName) {
|
|
166
|
+
throw StateError(
|
|
167
|
+
'Bucket name mismatch: expected "$bucketName", but found '
|
|
168
|
+
'"${parsed.bucket}".',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
final res = await bucket.exists(parsed.key);
|
|
172
|
+
if (res.data == false) return false;
|
|
173
|
+
if (res.error != null) {
|
|
174
|
+
throw StateError(_getErrorMessage(res.error));
|
|
175
|
+
}
|
|
176
|
+
await verifySigned(bucket: bucket, key: parsed.key);
|
|
177
|
+
return res.data as bool;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@override
|
|
181
|
+
Future<void> delete(String storageUri) async {
|
|
182
|
+
final parsed = parseSupabaseStorageUri(storageUri);
|
|
183
|
+
if (parsed.bucket != bucketName) {
|
|
184
|
+
throw StateError(
|
|
185
|
+
'Bucket name mismatch: expected "$bucketName", but found '
|
|
186
|
+
'"${parsed.bucket}".',
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
final res = await bucket.remove([parsed.key]);
|
|
190
|
+
if (res.error != null) {
|
|
191
|
+
final msg = res.message ?? _getErrorMessage(res.error);
|
|
192
|
+
if (msg.contains('not found')) {
|
|
193
|
+
throw StateError('Bundle not found');
|
|
194
|
+
}
|
|
195
|
+
throw StateError('Failed to delete bundle: $msg');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@override
|
|
200
|
+
Future<void> downloadFile(String storageUri, String filePath) async {
|
|
201
|
+
final parsed = parseSupabaseStorageUri(storageUri);
|
|
202
|
+
if (parsed.bucket != bucketName) {
|
|
203
|
+
throw StateError(
|
|
204
|
+
'Bucket name mismatch: expected "$bucketName", but found '
|
|
205
|
+
'"${parsed.bucket}".',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
final res = await bucket.download(parsed.key);
|
|
209
|
+
if (res.error != null) {
|
|
210
|
+
throw StateError('Failed to download bundle: ${res.message}');
|
|
211
|
+
}
|
|
212
|
+
if (res.data == null) {
|
|
213
|
+
throw StateError('Failed to download bundle');
|
|
214
|
+
}
|
|
215
|
+
final dir = filePath.substring(0, filePath.lastIndexOf(io.Platform.pathSeparator));
|
|
216
|
+
await io.Directory(dir).create(recursive: true);
|
|
217
|
+
await io.File(filePath).writeAsBytes(res.data!);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@override
|
|
221
|
+
Future<List<StorageObject>> listObjects([String? prefix]) async {
|
|
222
|
+
final res = await bucket.list(prefix);
|
|
223
|
+
if (res.error != null) {
|
|
224
|
+
throw StateError(
|
|
225
|
+
'Failed to list objects: ${_getErrorMessage(res.error)}',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
final data = res.data ?? const <SupabaseStorageObject>[];
|
|
229
|
+
return data
|
|
230
|
+
.map(
|
|
231
|
+
(o) => StorageObject(
|
|
232
|
+
key: o.key,
|
|
233
|
+
storageUri: 'supabase-storage://$bucketName/${o.key}',
|
|
234
|
+
size: o.size,
|
|
235
|
+
lastModifiedAt: o.lastModifiedAt != null
|
|
236
|
+
? DateTime.tryParse(o.lastModifiedAt!)
|
|
237
|
+
: null,
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
.toList();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@override
|
|
244
|
+
Future<void> deleteObjects(List<String> keys) async {
|
|
245
|
+
if (keys.isEmpty) return;
|
|
246
|
+
final res = await bucket.remove(keys);
|
|
247
|
+
if (res.error != null) {
|
|
248
|
+
final msg = res.message ?? _getErrorMessage(res.error);
|
|
249
|
+
if (!msg.contains('not found')) {
|
|
250
|
+
throw StateError('Failed to delete objects: $msg');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class RuntimeStorageProfileImpl implements RuntimeStorageProfile {
|
|
257
|
+
RuntimeStorageProfileImpl({
|
|
258
|
+
required this.bucket,
|
|
259
|
+
required this.resolveSignedUrl,
|
|
260
|
+
required this.bucketName,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
final SupabaseStorageBucketLike bucket;
|
|
264
|
+
final ResolveSignedUrl resolveSignedUrl;
|
|
265
|
+
final String bucketName;
|
|
266
|
+
|
|
267
|
+
@override
|
|
268
|
+
Future<String?> readText(String storageUri) async {
|
|
269
|
+
final parsed = parseSupabaseStorageUri(storageUri);
|
|
270
|
+
if (parsed.bucket != bucketName) {
|
|
271
|
+
throw StateError(
|
|
272
|
+
'Bucket name mismatch: expected "$bucketName", but found '
|
|
273
|
+
'"${parsed.bucket}".',
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
final res = await bucket.download(parsed.key);
|
|
277
|
+
if (res.error != null) {
|
|
278
|
+
final msg = res.message ?? _getErrorMessage(res.error);
|
|
279
|
+
if (msg.contains('not found')) return null;
|
|
280
|
+
throw StateError('Failed to read storage text: $msg');
|
|
281
|
+
}
|
|
282
|
+
if (res.data == null) return null;
|
|
283
|
+
return String.fromCharCodes(res.data!);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@override
|
|
287
|
+
Future<Map<String, String>> getDownloadUrl(String storageUri) async {
|
|
288
|
+
final parsed = parseSupabaseStorageUri(storageUri);
|
|
289
|
+
if (parsed.bucket != bucketName) {
|
|
290
|
+
throw StateError(
|
|
291
|
+
'Bucket name mismatch: expected "$bucketName", but found '
|
|
292
|
+
'"${parsed.bucket}".',
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
final signedUrl = await resolveSignedUrl(bucketName, parsed.key);
|
|
296
|
+
return {'fileUrl': signedUrl};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/// Faithful port of hot-updater `plugins/supabase/src/types.ts`.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
/// Row type from Supabase `bundles` table.
|
|
5
|
+
class SupabaseBundleRow {
|
|
6
|
+
final String id;
|
|
7
|
+
final String channel;
|
|
8
|
+
final bool enabled;
|
|
9
|
+
final bool shouldForceUpdate;
|
|
10
|
+
final String fileHash;
|
|
11
|
+
final String? gitCommitHash;
|
|
12
|
+
final String? message;
|
|
13
|
+
final String platform;
|
|
14
|
+
final String? targetAppVersion;
|
|
15
|
+
final String? fingerprintHash;
|
|
16
|
+
final String storageUri;
|
|
17
|
+
final Map<String, Object?>? metadata;
|
|
18
|
+
final String? manifestStorageUri;
|
|
19
|
+
final String? manifestFileHash;
|
|
20
|
+
final String? assetBaseStorageUri;
|
|
21
|
+
final int? rolloutCohortCount;
|
|
22
|
+
final List<String>? targetCohorts;
|
|
23
|
+
|
|
24
|
+
const SupabaseBundleRow({
|
|
25
|
+
required this.id,
|
|
26
|
+
required this.channel,
|
|
27
|
+
required this.enabled,
|
|
28
|
+
required this.shouldForceUpdate,
|
|
29
|
+
required this.fileHash,
|
|
30
|
+
this.gitCommitHash,
|
|
31
|
+
this.message,
|
|
32
|
+
required this.platform,
|
|
33
|
+
this.targetAppVersion,
|
|
34
|
+
this.fingerprintHash,
|
|
35
|
+
required this.storageUri,
|
|
36
|
+
this.metadata,
|
|
37
|
+
this.manifestStorageUri,
|
|
38
|
+
this.manifestFileHash,
|
|
39
|
+
this.assetBaseStorageUri,
|
|
40
|
+
this.rolloutCohortCount,
|
|
41
|
+
this.targetCohorts,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
factory SupabaseBundleRow.fromJson(Map<String, dynamic> j) =>
|
|
45
|
+
SupabaseBundleRow(
|
|
46
|
+
id: j['id'] as String,
|
|
47
|
+
channel: j['channel'] as String,
|
|
48
|
+
enabled: j['enabled'] as bool,
|
|
49
|
+
shouldForceUpdate: j['should_force_update'] as bool,
|
|
50
|
+
fileHash: j['file_hash'] as String,
|
|
51
|
+
gitCommitHash: j['git_commit_hash'] as String?,
|
|
52
|
+
message: j['message'] as String?,
|
|
53
|
+
platform: j['platform'] as String,
|
|
54
|
+
targetAppVersion: j['target_app_version'] as String?,
|
|
55
|
+
fingerprintHash: j['fingerprint_hash'] as String?,
|
|
56
|
+
storageUri: j['storage_uri'] as String,
|
|
57
|
+
metadata: j['metadata'] is Map
|
|
58
|
+
? (j['metadata'] as Map).cast<String, Object?>()
|
|
59
|
+
: null,
|
|
60
|
+
manifestStorageUri: j['manifest_storage_uri'] as String?,
|
|
61
|
+
manifestFileHash: j['manifest_file_hash'] as String?,
|
|
62
|
+
assetBaseStorageUri: j['asset_base_storage_uri'] as String?,
|
|
63
|
+
rolloutCohortCount: j['rollout_cohort_count'] as int?,
|
|
64
|
+
targetCohorts: (j['target_cohorts'] as List?)
|
|
65
|
+
?.map((e) => e as String)
|
|
66
|
+
.toList(),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
Map<String, dynamic> toJson() => {
|
|
70
|
+
'id': id,
|
|
71
|
+
'channel': channel,
|
|
72
|
+
'enabled': enabled,
|
|
73
|
+
'should_force_update': shouldForceUpdate,
|
|
74
|
+
'file_hash': fileHash,
|
|
75
|
+
'git_commit_hash': gitCommitHash,
|
|
76
|
+
'message': message,
|
|
77
|
+
'platform': platform,
|
|
78
|
+
'target_app_version': targetAppVersion,
|
|
79
|
+
'fingerprint_hash': fingerprintHash,
|
|
80
|
+
'storage_uri': storageUri,
|
|
81
|
+
'metadata': metadata,
|
|
82
|
+
'manifest_storage_uri': manifestStorageUri,
|
|
83
|
+
'manifest_file_hash': manifestFileHash,
|
|
84
|
+
'asset_base_storage_uri': assetBaseStorageUri,
|
|
85
|
+
'rollout_cohort_count': rolloutCohortCount,
|
|
86
|
+
'target_cohorts': targetCohorts,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Row type from Supabase `bundle_patches` table.
|
|
91
|
+
class SupabaseBundlePatchRow {
|
|
92
|
+
final String id;
|
|
93
|
+
final String bundleId;
|
|
94
|
+
final String baseBundleId;
|
|
95
|
+
final String baseFileHash;
|
|
96
|
+
final String patchFileHash;
|
|
97
|
+
final String patchStorageUri;
|
|
98
|
+
final int orderIndex;
|
|
99
|
+
|
|
100
|
+
const SupabaseBundlePatchRow({
|
|
101
|
+
required this.id,
|
|
102
|
+
required this.bundleId,
|
|
103
|
+
required this.baseBundleId,
|
|
104
|
+
required this.baseFileHash,
|
|
105
|
+
required this.patchFileHash,
|
|
106
|
+
required this.patchStorageUri,
|
|
107
|
+
required this.orderIndex,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
factory SupabaseBundlePatchRow.fromJson(Map<String, dynamic> j) =>
|
|
111
|
+
SupabaseBundlePatchRow(
|
|
112
|
+
id: j['id'] as String,
|
|
113
|
+
bundleId: j['bundle_id'] as String,
|
|
114
|
+
baseBundleId: j['base_bundle_id'] as String,
|
|
115
|
+
baseFileHash: j['base_file_hash'] as String,
|
|
116
|
+
patchFileHash: j['patch_file_hash'] as String,
|
|
117
|
+
patchStorageUri: j['patch_storage_uri'] as String,
|
|
118
|
+
orderIndex: j['order_index'] as int,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
Map<String, dynamic> toJson() => {
|
|
122
|
+
'id': id,
|
|
123
|
+
'bundle_id': bundleId,
|
|
124
|
+
'base_bundle_id': baseBundleId,
|
|
125
|
+
'base_file_hash': baseFileHash,
|
|
126
|
+
'patch_file_hash': patchFileHash,
|
|
127
|
+
'patch_storage_uri': patchStorageUri,
|
|
128
|
+
'order_index': orderIndex,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: flutter_patcher_supabase
|
|
2
|
+
description: Supabase plugin for flutter_patcher — database + storage backends.
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
publish_to: none
|
|
5
|
+
|
|
6
|
+
environment:
|
|
7
|
+
sdk: ">=3.3.0 <4.0.0"
|
|
8
|
+
|
|
9
|
+
dependencies:
|
|
10
|
+
flutter_patcher_core:
|
|
11
|
+
path: ../../packages/core
|
|
12
|
+
flutter_patcher_plugin_core:
|
|
13
|
+
path: ../../plugins/plugin-core
|
|
14
|
+
http: ^1.2.0
|
|
15
|
+
supabase: ^2.14.0
|
|
16
|
+
|
|
17
|
+
dev_dependencies:
|
|
18
|
+
test: ^1.25.0
|
|
19
|
+
lints: ^4.0.0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { createHotUpdater } from "@hot-updater/server";
|
|
3
|
+
import {
|
|
4
|
+
supabaseEdgeFunctionDatabase,
|
|
5
|
+
supabaseEdgeFunctionStorage,
|
|
6
|
+
} from "@hot-updater/supabase";
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
var HotUpdater: {
|
|
11
|
+
FUNCTION_NAME: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const functionName = HotUpdater.FUNCTION_NAME;
|
|
16
|
+
const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
|
|
17
|
+
const supabaseServiceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
|
|
18
|
+
const functionBasePath = `/${functionName}`;
|
|
19
|
+
const hotUpdaterBasePath = "/";
|
|
20
|
+
|
|
21
|
+
const hotUpdater = createHotUpdater({
|
|
22
|
+
database: supabaseEdgeFunctionDatabase({
|
|
23
|
+
supabaseUrl,
|
|
24
|
+
supabaseServiceRoleKey,
|
|
25
|
+
}),
|
|
26
|
+
storages: [
|
|
27
|
+
supabaseEdgeFunctionStorage({
|
|
28
|
+
supabaseUrl,
|
|
29
|
+
supabaseServiceRoleKey,
|
|
30
|
+
}),
|
|
31
|
+
],
|
|
32
|
+
basePath: hotUpdaterBasePath,
|
|
33
|
+
routes: {
|
|
34
|
+
updateCheck: true,
|
|
35
|
+
bundles: false,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const app = new Hono().basePath(functionBasePath);
|
|
40
|
+
|
|
41
|
+
app.get("/ping", (c) => c.text("pong"));
|
|
42
|
+
app.mount(hotUpdaterBasePath, hotUpdater.handler);
|
|
43
|
+
|
|
44
|
+
Deno.serve(app.fetch);
|