B4J Question FCM Notifications and Server handler

yiankos1

Well-Known Member
Licensed User
Longtime User
Hello,

I am trying to send notifications by creating a handler "sendNotification". If i put all code from b4j example then, only the first notification arrives correctly and the second throws a verification error:

b4j code:
B4X:
Sub Handle(req As ServletRequest, resp As ServletResponse)
    If req.Method <> "POST" Then
        resp.SendError(500, "Method not supported!")
        Return
    End If
        
    Private StreamReader As TextReader
    StreamReader.Initialize(req.InputStream)
    Private parser As JSONParser
    parser.Initialize(StreamReader.ReadAll)
    Private m As Map = parser.NextObject
    
    If m.Get("typeNotification") = "" Then
        resp.SendError(500,"Missing parameter!")
        Return
    End If
    
    Private Token As String = GetTokenValue(Main.ServiceAccountFilePath)
    
    sendNotification(m,token)
End Sub

Private Sub GetTokenValue (FilePath As String) As String
    Dim GoogleCredentials As JavaObject
    GoogleCredentials.InitializeStatic("com.google.auth.oauth2.GoogleCredentials")
    Dim Credentials As JavaObject = GoogleCredentials.RunMethodJO("fromStream", Array(File.OpenInput(FilePath, ""))) _
        .RunMethod("createScoped", Array(Array As String("https://www.googleapis.com/auth/firebase.messaging")))
    Credentials.RunMethod("refreshIfExpired", Null)
    Return Credentials.RunMethodJO("getAccessToken", Null).RunMethod("getTokenValue", Null)
End Sub

Sub sendNotification(dataMap As Map, Token As String) As ResumableSub
    
    Dim Job As HttpJob
    Job.Initialize("", Me)
    Dim data As Map = CreateMap("title": dataMap.Get("title"), "body": dataMap.Get("text"), "userSent": dataMap.Get("userSent"), _
    "userTo": dataMap.Get("userTo"),"typeNotification": dataMap.Get("typeNotification"),"selectedIdWorkout" : dataMap.Get("selectedIdWorkout"), _
    "chatID" : dataMap.Get("chatID"),"chatMessageID" : dataMap.Get("chatMessageID"))
    
    'Checking if device is ios or android in order to send the correct notification
    Private sql As SQL = DBM.GetSQL
    Private os As Int = sql.ExecQuerySingleResult2("SELECT os FROM users WHERE id = ?", Array As String(dataMap.Get("userTo")))
    DBM.CloseSQL(sql)
    
'    If dataMap.Get("userTo").As(String).StartsWith("ios_") Then
    If os = 1 Then
        'B4i
        Dim message As Map = CreateMap("topic": "ios_" & dataMap.Get("userTo"), "data": data)
        Dim Badge As Int = 0
        Dim iosalert As Map =  CreateMap("title": dataMap.Get("title"), "body": dataMap.Get("text"))
        message.Put("notification", iosalert)
        message.Put("apns", CreateMap("headers": _
            CreateMap("apns-priority": "10"), _
            "payload": CreateMap("aps": CreateMap("sound":"default", "badge": Badge))))
    Else
'        B4A
        Dim message As Map = CreateMap("topic": dataMap.Get("userTo"), "data": data)
        message.Put("android", CreateMap("priority": "high"))
    End If
    Dim jg As JSONGenerator
    jg.Initialize(CreateMap("message": message))
    Log(jg.ToPrettyString(4))
    Job.PostString($"https://fcm.googleapis.com/v1/projects/${Main.ProjectId}/messages:send"$, jg.ToString)
    Job.GetRequest.SetContentType("application/json;charset=UTF-8")
    Job.GetRequest.SetHeader("Authorization", "Bearer " & Token)
    Wait For (Job) JobDone(Job As HttpJob)
    If Job.Success Then
        Log(Job.GetString)
    End If
    Job.Release

End Sub

server output:
B4X:
{
    "message": {
        "data": {
            "chatID": "06348c10-cf37-11ef-9bd1-52894e2c9ee7",
            "typeNotification": "chat",
            "chatMessageID": "5ef8e312-9e8c-a169-8491-68efeff6303f",
            "title": "Lorem Ipsum",
            "body": "e",
            "selectedIdWorkout": null,
            "userTo": "d5b3ac4f-f191-11ee-ae89-a8a159113189",
            "userSent": "e4e8eec0-5a42-11ef-a763-52894e2c9ee7"
        },
        "android": {
            "priority": "high"
        },
        "topic": "d5b3ac4f-f191-11ee-ae89-a8a159113189"
    }
}
Command: query: getLastChatMessage, took: 43ms, client=[0:0:0:0:0:0:0:1]
Command: batch (size=1), took: 31ms, client=[0:0:0:0:0:0:0:1]
Command: batch (size=1), took: 68ms, client=[0:0:0:0:0:0:0:1]
Command: batch (size=1), took: 88ms, client=[0:0:0:0:0:0:0:1]
{
    "message": {
        "data": {
            "chatID": "06348c10-cf37-11ef-9bd1-52894e2c9ee7",
            "typeNotification": "chat",
            "chatMessageID": "aa0fb71c-483c-aa47-8338-498347c0a749",
            "title": "Lorem Ipsum",
            "body": "poo",
            "selectedIdWorkout": null,
            "userTo": "d5b3ac4f-f191-11ee-ae89-a8a159113189",
            "userSent": "e4e8eec0-5a42-11ef-a763-52894e2c9ee7"
        },
        "android": {
            "priority": "high"
        },
        "topic": "d5b3ac4f-f191-11ee-ae89-a8a159113189"
    }
}
ResponseError. Reason: , Response: {
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "CREDENTIALS_MISSING",
        "domain": "googleapis.com",
        "metadata": {
          "method": "google.firebase.fcm.v1.FcmService.SendMessage",
          "service": "fcm.googleapis.com"
        }
      }
    ]
  }
}

The only way that i get it working is by putting the sendNotification sub at main page and calling it with CallSubDelayed3(Main,"sendNotification",m,token). The problem here is that after a couple of days it stops to send everything without any error. Just does not send.

B4X gpt mentions these:

Understanding the Problem

  1. Token Expiration:
    • Access tokens retrieved via GoogleCredentials (in your GetTokenValue method) are valid for a short period (usually 1 hour). If you attempt to reuse an expired token, FCM will return a 401 error with the "UNAUTHENTICATED" status.
  2. CallSubDelayed Workaround:
    • While using CallSubDelayed to move the logic to the Main module avoids some issues (such as threading), it doesn't solve the root problem of token expiration. Moreover, over time, server resources may become limited due to improperly handled asynchronous calls, which might explain why FCM notifications stop working after a few days.
  3. Silent Failure:
    • The absence of error logs after running for days suggests that some part of the process (e.g., token generation or notification sending) is failing silently. This can happen if exceptions are not being logged or if threads are being mismanaged.

Why CallSubDelayed Works

  • Thread Safety:
    • CallSubDelayed executes the target subroutine in the main thread (UI thread in B4X terms). This eliminates concurrency issues or conflicts caused by multiple threads trying to refresh the token or send notifications simultaneously.
    • It avoids blocking the JRDC handler thread, which could result in unexpected behavior or timeout issues.
  • Message Loop:
    • CallSubDelayed ensures the method is executed with a proper message loop. This is important for Wait For to work correctly in subs that use Sleep or handle asynchronous jobs.

Why Notifications Stop After a Few Days

If notifications stop working after a few days of server runtime, possible causes include:

  • Expired Tokens:
    • If tokens aren't refreshed properly, Firebase will reject requests. Ensure your GetTokenValue logic works consistently.
  • Thread Issues:
    • JRDC threads may get stuck if handlers aren't properly returning control. Using CallSubDelayed avoids this problem by delegating heavy logic to the Main module.
  • Resource Leaks:
    • Ensure every HttpJob is released (Job.Release) after it completes to prevent resource exhaustion.

Any ideas are welcome.

Thank you for your time.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
 
Upvote 0

yiankos1

Well-Known Member
Licensed User
Longtime User
Hello Erel,

Even with StartMessageLoop and StopMessageLoop, the "UNAUTHENTICATED" error keeps showing at the second and after notification.

If I did not send notification for a couple of minutes, then notifcation sent successfully (just for that one, not the next one again).

The only working solution is by doing single theard handler:
B4X:
srvr.AddHandler("/notification", "sendNotification", True)

Are there any other downturns except from queuing notifications?

Thank you for your time.
 
Upvote 0
Top