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:
server output:
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:
Any ideas are welcome.
Thank you for your time.
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
- 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.
- 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.
- 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.