firebase / firebase-admin-dotnet

Firebase Admin .NET SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
356 stars 128 forks source link

Best way to determine specific errors? #45

Closed jstansbe closed 4 years ago

jstansbe commented 5 years ago

I'm trying to determine when there is an invalid token, an expired token, message rate exceeded error, etc. (https://firebase.google.com/docs/cloud-messaging/send-message#admin_sdk_error_reference).

When i send a single message/notification to a token like so: var response = await FirebaseMessaging.DefaultInstance.SendAsync(message);

I get the following response (I'm getting this when i catch a thrown exception from FirebaseMessaging... I think i must be using this incorrectly as this seems strange... shouldn't there be a response object?).

What am I doing that is incorrect? Shouldn't I be able to find a code that matches to whats in the admin_sdk_error_reference section? (link i have above)

Response status code does not indicate success: 400 (BadRequest)
{
  "error": {
    "code": 400,
    "message": "The registration token is not a valid FCM registration token",
    "errors": [
      {
        "message": "The registration token is not a valid FCM registration token",
        "domain": "global",
        "reason": "badRequest"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}
hiranya911 commented 5 years ago

Hi @jstansbe. You're not doing anything wrong. We don't expose any additional error details in the SDK at the moment. There's just a simple FirebaseException type that exposes a string, and this string may also include the full HTTP response text (which is what you're seeing).

There's a broader effort in progress to improve how errors are exposed in the SDK (it's being proposed across all of our SDKs). It's still at very early stages, and I don't have a concrete timeline for you. But when that effort lands, you will be able to easily check for error codes, and handle your errors accordingly.

The proposed API is something like:

class FirebaseException extends Exception {
  ErrorCode Code;
  String Message;
  HttpResponseMessage? HttpResponse; // System.Net.Http.HttpResponseMessage
  Exception InnerException;
}

class FirebaseMessagingException extends FirebaseException {
  MessagingErrorCode? MessagingErrorCode;
}

Unfortunately this means at the moment, you have to split and parse the exception message yourself to get any meaningful information out of it.

I should also point out that the FCM errors codes in the documentation are only really supported in our Node.js SDK. None of the other SDKs ever supported them, and they will most likely go away completely when the new error handling proposal is implemented (or they will get absorbed to the new MessagingErrorCode type mentioned above).

swebgit commented 5 years ago

Thanks for the info regarding this @hiranya911. I'm looking forward to being able to handle error codes from the Admin SDK more easily in .NET!

KalleOlaviNiemitalo commented 5 years ago
  HttpResponseMessage? HttpResponse; // System.Net.Http.HttpResponseMessage

Cool, my application can then get Retry-After from there, like the documentation of messaging/server-unavailable says it must do.

Unfortunately this means at the moment, you have to split and parse the exception message yourself to get any meaningful information out of it.

I think my application needs to search for the first newline, check that it is followed by "{", and parse JSON starting from there. I'd also like it to check that the HTTP response had Content-Type: application/json but it seems the library does not yet surface that information.

I should also point out that the FCM errors codes in the documentation are only really supported in our Node.js SDK.

I originally got the impression that those error codes would appear directly in the HTTP response. Can Google please clarify the documentation? I think keeping this issue open on GitHub may also help other users of the SDK figure the problem out faster.

hiranya911 commented 5 years ago

Cool, my application can then get Retry-After from there, like the documentation of messaging/server-unavailable says it must do.

In the future the SDK will automatically handle retries. This is already implemented in our Java, Python and Go SDKs, and being currently implemented in Node.js (see https://github.com/firebase/firebase-admin-node/pull/518). C# SDK will also have the same feature eventually.

I think my application needs to search for the first newline, check that it is followed by "{", and parse JSON starting from there. I'd also like it to check that the HTTP response had Content-Type: application/json but it seems the library does not yet surface that information.

You don't have to do any of that. System.Net.Http.HttpResponseMessage exposes the headers and content separately: https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage?view=netframework-4.8

I originally got the impression that those error codes would appear directly in the HTTP response.

The error codes currently in our documentation are not available on the actual HTTP responses. They are added by the SDKs. We are also working towards removing these SDK-specific error codes. Our new error handling regime will be aligned with https://cloud.google.com/apis/design/errors. These new errors codes will be present on the HTTP responses.

KalleOlaviNiemitalo commented 5 years ago

You don't have to do any of that.

I meant my application needs to search for a newline when it is still using FirebaseAdmin 1.4.0 or earlier.

ghost commented 5 years ago

Just ran into this issue as well. Having to parse JSON in an exception message is kind of ridiculous. The solution that @hiranya911 described sounds great, but I'm confused why at least the FirebaseMessagingException from the Java SDK hasn't been added in the meantime.

hiranya911 commented 4 years ago

Here's the finalized design for the new exception types:

namespace FirebaseAdmin
  enum ErrorCode
    InvalidArgument
    FailedPrecondition
    OutOfRange
    Internal
    … other error codes 
  class FirebaseException : Exception
    ErrorCode ErrorCode { get; }
    System.Net.Http.HttpResponseMessage HttpResponse { get; }
    string Message { get; } // inherited
    Exception InnerException { get; }  // inherited

namespace FirebaseAdmin.Auth
  enum AuthErrorCode
    InvalidIdToken
    ExpiredIdToken
    TokenSignFailed
    CertificateFetchFailed
    UserNotFound
    DuplicateLocalId
    … other FirebaseAuth error codes
  class FirebaseAuthException : FirebaseException
    AuthErrorCode? AuthErrorCode

namespace FirebaseAdmin.Messaging
  enum MessagingErrorCode
    ThirdPartyAuthError
    Unregistered
    Internal
    InvalidArgument
    SenderIdMismatch
    Unavailable
    QuotaExceeded
  class FirebaseMessagingException : FirebaseException
    MessagingErrorCode? MessagingErrorCode
swebgit commented 4 years ago

@hiranya911 Will the new exception structures be included in the next release of the SDK?

hiranya911 commented 4 years ago

@swebgit They will be released incrementally over a series of releases.

hiranya911 commented 4 years ago

Note that the FCM APIs now support the fine-grained error handling scheme outlined above. FirebaseMessagingException and MessagingErrorCode types were included in the v1.8 release. More to come.

ghost commented 4 years ago

@hiranya911 So if we get a MessagingErrorCode.InvalidArgument, what is the recommended way to determine which argument was invalid? Is the only option still to parse the exception message as JSON?

hiranya911 commented 4 years ago

A lot of the time, the error message indicates what went wrong.

FirebaseAdmin.Messaging.FirebaseMessagingException : The registration token is not a valid FCM registration token

This is directly extracted from the error response sent by FCM:

{
  "error": {
    "code": 400,
    "message": "The registration token is not a valid FCM registration token",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.firebase.fcm.v1.FcmError",
        "errorCode": "INVALID_ARGUMENT"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "message.token",
            "description": "The registration token is not a valid FCM registration token"
          }
        ]
      }
    ]
  }
}

If a certain INVALID_ARGUMENT error message doesn't have enough details, let us know by filing an issue (please provide enough details for us to repro the behavior), and we can work with the FCM team to get the necessary details included in the FCM server response.

Also note that we don't expect the clients to "handle" this sort of errors at runtime. INVALID_ARGUMENT errors are (usually) client errors and therefore should be "fixed" by the developers as opposed to "handling". A typical course of action at runtime would be to log the error message and other useful information, so the problem can be easily debugged and fixed.

In a future release (most likely the next release), clients will also have direct access to the HttpResponse object responsible for this type of errors, so they can dump all the relevant information (headers, payload etc).

KalleOlaviNiemitalo commented 4 years ago

Suppose a mobile app has sent a registration token to my server, which then tries to send FCM messages to the mobile app, but the token turns out to be invalid. In this case, I'd like my server to recognize the error so that it can stop trying to use the invalid token for any other messages. Should the server then recognize the "The registration token is not a valid FCM registration token" string in the exception? This won't be localized?

ghost commented 4 years ago

My use case is pretty much identical to @KalleOlaviNiemitalo's. Being able to recognize invalid tokens at runtime is pretty key, and I believe the response for invalid tokens is InvalidArgument.

hiranya911 commented 4 years ago

As shown in my previous comment, the FCM server response doesn't provide any more granular information in this case. But I do see the point of your use case. Perhaps it makes sense to introduce a new type.googleapis.com/google.firebase.fcm.v1.FcmError error code to signal invalid FCM tokens (this should be done in the FCM server first).

Can I ask one of you to file a new issue describing this use case?

pajej-dev commented 4 years ago

dead @hiranya911, In my .Net client I am using following method:

string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);

  1. How can I recognize that my message was sent successfully and delivered to client app?
  2. How can I recognize that FCM server was unavailable? Should I try{} catch{} this message and check for some specific error? If yes, pls give me actual error codes, because I Cant reproduce FCM server failures :)

Add.2. I want be able to retry message only if there was some server errors - not business validation such as wrong device ID , etc.

hiranya911 commented 4 years ago
  1. There's no way to detect if a message was delivered to a client app or not. FCM is an asynchronous messaging system. You can only know if a message was successfully handed off to FCM or not (indicated by the return value or the exception raised by the SDK). Client device delivery depends on many factors, some of which are not under your or even Firebase's control (e.g. the target device may be down, or the user may have uninstall the app).

  2. MessagingErrorCode.Unavailable is the error code.

mickeahlinder commented 4 years ago

A lot of the time, the error message indicates what went wrong.

FirebaseAdmin.Messaging.FirebaseMessagingException : The registration token is not a valid FCM registration token

This is directly extracted from the error response sent by FCM:

{
  "error": {
    "code": 400,
    "message": "The registration token is not a valid FCM registration token",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.firebase.fcm.v1.FcmError",
        "errorCode": "INVALID_ARGUMENT"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "message.token",
            "description": "The registration token is not a valid FCM registration token"
          }
        ]
      }
    ]
  }
}

If a certain INVALID_ARGUMENT error message doesn't have enough details, let us know by filing an issue (please provide enough details for us to repro the behavior), and we can work with the FCM team to get the necessary details included in the FCM server response.

Also note that we don't expect the clients to "handle" this sort of errors at runtime. INVALID_ARGUMENT errors are (usually) client errors and therefore should be "fixed" by the developers as opposed to "handling". A typical course of action at runtime would be to log the error message and other useful information, so the problem can be easily debugged and fixed.

In a future release (most likely the next release), clients will also have direct access to the HttpResponse object responsible for this type of errors, so they can dump all the relevant information (headers, payload etc).

I might have misunderstood something here but the response I get from FCM for the exact same error does not include the details part of the response that you have in your example. Is there a specific configuration needed to get the detailed errors, or did you mean that the details section will also be available in an upcoming release of FCM?

hiranya911 commented 4 years ago

@mickeahlinder You need #107 for that. That's not yet released.

mickeahlinder commented 4 years ago

Yes, Thank you. After some further research I figured that out. I managed to set that header myself using a DelegatingHandler and managed to get the MessagingErrorCode populated on the FirebaseMessagingException, so it seems to be working, or am I just lucky? Is it brittle to use that header along with the 1.8.0 release?

hiranya911 commented 4 years ago

@mickeahlinder Nice 👍 The SDK already has the correct parsing logic to handle the details section of the error response. So you're all set. You can remove your custom handler, when you upgrade to a newer version of the SDK in the future.

mickeahlinder commented 4 years ago

Cool, thanks for your swift replies. Much appreciated! 😀

james-perfectserve commented 4 years ago

@mickeahlinder how did you set up the Delegating Handler? looks like the code that adds the header which results in FirebaseMessagingException.MessagingErrorCode getting populated was added 7 days after 1.8.0 was published and no new packages have been published yet.

or

@hiranya911 could we get a 1.8.1 package published now that the request.Headers.Add("X-GOOG-API-FORMAT-VERSION", "2"); header is getting set?

hiranya911 commented 4 years ago

This issue is completely addressed as of the v1.9.0 release.

satyenderkumar071 commented 2 years ago

what are the possible errors need to be handled to make it fault tolerant?

ASH247iT commented 2 years ago

Hi - any suggestions what to do when it only says "message": "Internal error encountered." "errorCode": "INTERNAL"

Is that a problem within Firebase or a config problem It has been working OK upto now