diff --git a/packages/dart_firebase_admin/lib/src/storage/storage.dart b/packages/dart_firebase_admin/lib/src/storage/storage.dart index 77320d31..e558eca0 100644 --- a/packages/dart_firebase_admin/lib/src/storage/storage.dart +++ b/packages/dart_firebase_admin/lib/src/storage/storage.dart @@ -1,36 +1,14 @@ import 'dart:convert'; import 'package:google_cloud_storage/google_cloud_storage.dart' as gcs; -import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; import '../app.dart'; +import '../utils/native_environment.dart'; part 'storage_exception.dart'; -/// An [http.BaseClient] that lazily resolves the real client on each request. -/// -/// This allows [Storage] to be constructed synchronously even though -/// [FirebaseApp.client] is asynchronous. The underlying client lifecycle is -/// managed externally (by [FirebaseApp]), so [close] is a no-op here. -class _DeferredHttpClient extends http.BaseClient { - _DeferredHttpClient(this._clientFuture); - - final Future _clientFuture; - - @override - Future send(http.BaseRequest request) async { - return (await _clientFuture).send(request); - } - - @override - void close() { - // The underlying client is managed externally; do not close it here. - } -} - class Storage implements FirebaseService { Storage._(this.app) { - String? apiEndpoint; final isEmulator = Environment.isStorageEmulatorEnabled(); if (isEmulator) { @@ -42,25 +20,9 @@ class Storage implements FirebaseService { 'FIREBASE_STORAGE_EMULATOR_HOST should not contain a protocol (http or https).', ); } - // The new gcs.Storage adds the http:// scheme automatically when - // useAuthWithCustomEndpoint is false, so pass only host:port. - apiEndpoint = emulatorHost; + setNativeEnvironmentVariable('STORAGE_EMULATOR_HOST', emulatorHost); } - - // For emulator, use the provided httpClient (e.g. a mock in tests) or a - // plain unauthenticated client. For production, wrap the async auth client - // in a _DeferredHttpClient so construction stays synchronous. - final http.Client httpClient = isEmulator - ? _DeferredHttpClient( - Future.value(app.options.httpClient ?? http.Client()), - ) - : _DeferredHttpClient(app.client); - - _delegate = gcs.Storage( - client: httpClient, - apiEndpoint: apiEndpoint, - useAuthWithCustomEndpoint: false, - ); + _delegate = gcs.Storage(); } @internal @@ -73,7 +35,7 @@ class Storage implements FirebaseService { late final gcs.Storage _delegate; - gcs.Bucket bucket(String? name) { + gcs.Bucket bucket([String? name]) { final bucketName = name ?? app.options.storageBucket; if (bucketName == null || bucketName.isEmpty) { throw FirebaseAppException( @@ -134,8 +96,6 @@ class Storage implements FirebaseService { @override Future delete() async { - // _delegate.close() calls close() on our _DeferredHttpClient, which is a - // no-op, so the externally-managed http client is not closed here. _delegate.close(); } } diff --git a/packages/dart_firebase_admin/lib/src/utils/native_environment.dart b/packages/dart_firebase_admin/lib/src/utils/native_environment.dart new file mode 100644 index 00000000..2e123298 --- /dev/null +++ b/packages/dart_firebase_admin/lib/src/utils/native_environment.dart @@ -0,0 +1,46 @@ +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:meta/meta.dart'; + +import '../../dart_firebase_admin.dart'; + +final int Function(Pointer, Pointer, int) _setenv = + DynamicLibrary.process().lookupFunction< + Int32 Function(Pointer, Pointer, Int32), + int Function(Pointer, Pointer, int) + >('setenv'); + +final int Function(Pointer, Pointer) _setEnvironmentVariableW = + DynamicLibrary.open('kernel32.dll').lookupFunction< + Int32 Function(Pointer, Pointer), + int Function(Pointer, Pointer) + >('SetEnvironmentVariableW'); + +@internal +void setNativeEnvironmentVariable(String name, String value) { + if (Platform.isWindows) { + using((arena) { + final namePtr = name.toNativeUtf16(allocator: arena); + final valuePtr = value.toNativeUtf16(allocator: arena); + if (_setEnvironmentVariableW(namePtr, valuePtr) == 0) { + throw FirebaseAppException( + AppErrorCode.internalError, + 'Failed to set native environment variable: $name', + ); + } + }); + } else { + using((arena) { + final namePtr = name.toNativeUtf8(allocator: arena); + final valuePtr = value.toNativeUtf8(allocator: arena); + if (_setenv(namePtr, valuePtr, 1) == -1) { + throw FirebaseAppException( + AppErrorCode.internalError, + 'Failed to set native environment variable: $name', + ); + } + }); + } +} diff --git a/packages/dart_firebase_admin/pubspec.yaml b/packages/dart_firebase_admin/pubspec.yaml index 667bb135..fed17bbf 100644 --- a/packages/dart_firebase_admin/pubspec.yaml +++ b/packages/dart_firebase_admin/pubspec.yaml @@ -14,9 +14,12 @@ dependencies: collection: ^1.18.0 dart_jsonwebtoken: ^3.0.0 equatable: ^2.0.7 + ffi: ^2.1.2 google_cloud: ^0.3.0 google_cloud_firestore: ^0.1.0 - google_cloud_storage: ^0.5.1 + # `google_cloud_storage` will not change the surface used by + # `dart_firebase_admin`. + google_cloud_storage: ">=0.5.1" googleapis: ^15.0.0 googleapis_auth: ^2.2.0 googleapis_beta: ^9.0.0