Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions packages/dart_firebase_admin/lib/src/app/app_exception.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
part of '../app.dart';

/// Exception thrown for Firebase app initialization and lifecycle errors.
class FirebaseAppException implements Exception {
class FirebaseAppException extends FirebaseAdminException {
FirebaseAppException(this.errorCode, [String? message])
: code = errorCode.code,
_message = message;
: super('app', errorCode.code, message ?? errorCode.message);

/// The error code object containing code and default message.
final AppErrorCode errorCode;

/// The error code string.
final String code;

/// Custom error message, if provided.
final String? _message;

/// The error message. Returns custom message if provided, otherwise default.
String get message => _message ?? errorCode.message;

@override
String toString() => 'FirebaseAppException($code): $message';
}
Expand Down
31 changes: 31 additions & 0 deletions packages/dart_firebase_admin/lib/src/app/exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ class FirebaseArrayIndexError {

/// The error object.
final FirebaseAdminException error;

/// Converts this error to a JSON-serializable map.
///
/// This is useful for structured logging and error reporting.
/// The returned map contains:
/// - `index`: The index of the errored item
/// - `error`: The serialized error object (with code and message)
Map<String, dynamic> toJson() {
return {'index': index, 'error': error.toJson()};
}
}

/// A set of platform level error codes.
Expand Down Expand Up @@ -78,6 +88,27 @@ abstract class FirebaseAdminException implements Exception {
/// this message should not be displayed in your application.
String get message => _message ?? _platformErrorCodeMessage(_code);

/// Converts this exception to a JSON-serializable map.
///
/// This is useful for structured logging and error reporting in GCP Cloud Logging.
/// The returned map contains:
/// - `code`: The error code string (e.g., "auth/invalid-uid")
/// - `message`: The error message
///
/// Example:
/// ```dart
/// try {
/// // ...
/// } catch (e) {
/// if (e is FirebaseAdminException) {
/// print(jsonEncode(e.toJson())); // Logs structured JSON
/// }
/// }
/// ```
Map<String, dynamic> toJson() {
return {'code': code, 'message': message};
}

@override
String toString() {
return '$runtimeType($code, $message)';
Expand Down
35 changes: 31 additions & 4 deletions packages/dart_firebase_admin/lib/src/app_check/app_check.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,27 @@ class AppCheck implements FirebaseService {
return app.getOrInitService(FirebaseServiceType.appCheck.name, AppCheck._);
}

AppCheck._(this.app, {@internal AppCheckRequestHandler? requestHandler})
: _requestHandler = requestHandler ?? AppCheckRequestHandler(app);
AppCheck._(this.app)
: _requestHandler = AppCheckRequestHandler(app),
_tokenGenerator = AppCheckTokenGenerator(app.createCryptoSigner()),
_appCheckTokenVerifier = AppCheckTokenVerifier(app);

@internal
AppCheck.internal(
this.app, {
AppCheckRequestHandler? requestHandler,
AppCheckTokenGenerator? tokenGenerator,
AppCheckTokenVerifier? tokenVerifier,
}) : _requestHandler = requestHandler ?? AppCheckRequestHandler(app),
_tokenGenerator =
tokenGenerator ?? AppCheckTokenGenerator(app.createCryptoSigner()),
_appCheckTokenVerifier = tokenVerifier ?? AppCheckTokenVerifier(app);

@override
final FirebaseApp app;
final AppCheckRequestHandler _requestHandler;
late final _tokenGenerator = AppCheckTokenGenerator(app.createCryptoSigner());
late final _appCheckTokenVerifier = AppCheckTokenVerifier(app);
final AppCheckTokenGenerator _tokenGenerator;
final AppCheckTokenVerifier _appCheckTokenVerifier;

/// Creates a new [AppCheckToken] that can be sent
/// back to a client.
Expand All @@ -43,6 +56,13 @@ class AppCheck implements FirebaseService {
String appId, [
AppCheckTokenOptions? options,
]) async {
if (appId.isEmpty) {
throw FirebaseAppCheckException(
AppCheckErrorCode.invalidArgument,
'`appId` must be a non-empty string.',
);
}

final customToken = await _tokenGenerator.createCustomToken(appId, options);

return _requestHandler.exchangeToken(customToken, appId);
Expand All @@ -61,6 +81,13 @@ class AppCheck implements FirebaseService {
String appCheckToken, [
VerifyAppCheckTokenOptions? options,
]) async {
if (appCheckToken.isEmpty) {
throw FirebaseAppCheckException(
AppCheckErrorCode.invalidArgument,
'`appCheckToken` must be a non-empty string.',
);
}

final decodedToken = await _appCheckTokenVerifier.verifyToken(
appCheckToken,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ part of 'app_check.dart';
/// Handles HTTP client management, googleapis API client creation,
/// path builders, and simple API operations.
/// Does not handle emulator routing as App Check has no emulator support.
@internal
class AppCheckHttpClient {
AppCheckHttpClient(this.app);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ part of 'app_check.dart';
///
/// Handles complex business logic, request/response transformations,
/// and validation. Delegates simple API calls to [AppCheckHttpClient].
@internal
class AppCheckRequestHandler {
AppCheckRequestHandler(FirebaseApp app)
: _httpClient = AppCheckHttpClient(app);
Expand Down
18 changes: 9 additions & 9 deletions packages/dart_firebase_admin/test/app/app_registry_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ void main() {
() => registry.fetchOptionsFromEnvironment(),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'invalid-argument')
.having((e) => e.code, 'code', 'app/invalid-argument')
.having(
(e) => e.message,
'message',
Expand All @@ -188,7 +188,7 @@ void main() {
() => registry.fetchOptionsFromEnvironment(),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'invalid-argument')
.having((e) => e.code, 'code', 'app/invalid-argument')
.having(
(e) => e.message,
'message',
Expand Down Expand Up @@ -238,7 +238,7 @@ void main() {
isA<FirebaseAppException>().having(
(e) => e.code,
'code',
'invalid-app-options',
'app/invalid-app-options',
),
),
);
Expand All @@ -263,7 +263,7 @@ void main() {
isA<FirebaseAppException>().having(
(e) => e.code,
'code',
'invalid-app-options',
'app/invalid-app-options',
),
),
);
Expand Down Expand Up @@ -306,7 +306,7 @@ void main() {
isA<FirebaseAppException>().having(
(e) => e.code,
'code',
'duplicate-app',
'app/duplicate-app',
),
),
);
Expand Down Expand Up @@ -365,7 +365,7 @@ void main() {
),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'invalid-app-name')
.having((e) => e.code, 'code', 'app/invalid-app-name')
.having(
(e) => e.message,
'message',
Expand All @@ -380,7 +380,7 @@ void main() {
() => registry.getApp(''),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'invalid-app-name')
.having((e) => e.code, 'code', 'app/invalid-app-name')
.having(
(e) => e.message,
'message',
Expand Down Expand Up @@ -417,7 +417,7 @@ void main() {
() => registry.getApp(),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'no-app')
.having((e) => e.code, 'code', 'app/no-app')
.having(
(e) => e.message,
'message',
Expand All @@ -435,7 +435,7 @@ void main() {
() => registry.getApp('my-app'),
throwsA(
isA<FirebaseAppException>()
.having((e) => e.code, 'code', 'no-app')
.having((e) => e.code, 'code', 'app/no-app')
.having(
(e) => e.message,
'message',
Expand Down
Loading
Loading