B4J Question PushServer: iOS notifications don't always go

Marco Iannaccone

Member
Licensed User
Longtime User
I've been using the latest version of the PushServer, both in development and in production, both with iOS and Android devices, with two small changes (I added a title and an ID fields to the notifications sent), with success, but usually have a problem with iOS.

I must usually send the notification 2 (sometimes 3) times for the server to actually send it to iOS devices, and this is a problem, because it doesn't have problems with Android devices, that then receive the same notification multiple times.

This happens both with Apple's dev and production push notification server and, from the logs, I can't see any difference between when the notification works and when it doesn't. I get no error or anything else.
I'm pretty sure that, when the notification doesn't work and I have to send it again, it doesn't work for all devices, not just for some: it seems the server isn't sending it at all (but I get the response message saying the notification was sent).

I suppose the problem is not related to a malformed notification, since if I try to send the same notification immediately after (just by refreshing the browser's page), it works.

Is it possible that the PushServer periodically drops the connection to Apple's push server and estabilishes it again whith the second notification attempt, or maybe it doesn't correctly handle a connection problem when sending the notification?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
I didn't encounter this issue. What I did encounter is that the connection will break on the first invalid token. You should make sure that the feedback service is working properly and to get the tokens from the database in descending order (based on the date).

I do have a production app that uses the push server and it works properly.

Note that in the long run I recommend you to switch to Firebase. It is simpler and will probably work better.
 
Upvote 0

Marco Iannaccone

Member
Licensed User
Longtime User
In the lung run I'd actually like to migrate to Firebase but, in the meanwhile, can I, somehow, check if the connection with Apple servers actually went ok and, if not, send the push again, only for iOS devices, directly?
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
If I remember correct, the AStream_Error event will be raised in that case.

Check the logs for "error: <message here>".

You will not know which token caused the problem. One possible solution is to split the tokens into many small groups and send each group separately.

Check this code:
B4X:
Sub Process_Globals
   Private sock As Socket
   Private astream As AsyncStreams
   Private bc As ByteConverter
   Private timer1 As Timer
   Type Message(Text As String, Sound As Boolean, Badge As Int, ExpirationDate As Long, HighPriority As Boolean, Title As String)
   Private queue As List
   Private queueTimer As Timer
   Type IOS_DeviceAndMessage(Device As String, Msg As Message)
   Private connected As Boolean
   Private const NUMBER_OF_TOKENS_PER_REQUEST As Int = 5
End Sub

Public Sub Start
   bc.LittleEndian = False
   queue.Initialize
   queueTimer.Initialize("queueTimer", 500)
   timer1.Initialize("timer1", 3000) 'this timer is used when trying to reconnect
   Connect
End Sub

Private Sub Connect
   If sock.IsInitialized Then sock.Close
   Dim sock As Socket
   sock.InitializeSSL("sock", File.OpenInput(Yodfat.config.Get("iPushKeystore"), ""), Yodfat.config.Get("iPushKeystorePassword"))
   sock.Connect(Yodfat.config.Get("iGateway"), Yodfat.config.Get("iGateWayPort"), 30000)
End Sub

Private Sub sock_Connected (Successful As Boolean)
   If Successful Then
     timer1.Enabled = False
     Log("Socket connected.")
     Dim jo As JavaObject = sock
     jo.GetFieldJO("socket").RunMethod("setTcpNoDelay", Array(True))
     If astream.IsInitialized Then astream.Close
     astream.Initialize(sock.InputStream, sock.OutputStream, "astream")
     connected = True
     queueTimer.Enabled = True
   Else
     Log("Error connecting socket: " & LastException)
     Reconnect
   End If
End Sub
Private Sub Reconnect
   Log("Trying to reconnect...")
   connected = False
   timer1.Enabled = True
End Sub
Private Sub Timer1_Tick
   timer1.Enabled = False
   Connect
End Sub

Private Sub astream_NewData (Buffer() As Byte)
   If Buffer.Length >=6 Then
     Log("status=" & Buffer(1))
   Else
     Log("Invalid response")
   End If
End Sub
Private Sub astream_Error
   Log("error: " & LastException)
   Reconnect
End Sub
Private Sub astream_Terminated
   Log("terminated")
   Reconnect
End Sub

Public Sub SendMessageTo(Devices As List, msg As Message)
   Dim out As OutputStream
   out.InitializeToBytesArray(0)
   For Each device As String In Devices
     Dim dm As IOS_DeviceAndMessage
     dm.Initialize
     dm.Device = device
     dm.Msg = msg
     queue.Add(dm)
   Next
   queueTimer.Enabled = True
End Sub

Private Sub QueueTimer_Tick
   Log($"QueueTimer_Tick, queue size=${queue.Size}."$)
   If Not(connected) Then Return
   If queue.Size = 0 Then
     queueTimer.Enabled = False
     Return
   End If
   
   Dim out As OutputStream
   out.InitializeToBytesArray(0)
   Dim numberOfTokens As Int = 1
   Do While queue.Size > 0 And numberOfTokens <= NUMBER_OF_TOKENS_PER_REQUEST
     numberOfTokens = numberOfTokens + 1
     Dim dm As IOS_DeviceAndMessage = queue.Get(0)
     queue.RemoveAt(0)
     Log(dm.Device)
     Dim FrameData As OutputStream
     FrameData.InitializeToBytesArray(0)
     WriteItem(FrameData, 1, bc.HexToBytes(dm.device))
     Dim jg As JSONGenerator
     Dim m As Map =  CreateMap("alert": CreateMap("title": dm.Msg.Title, "body": dm.msg.Text), "badge": dm.msg.Badge)
     If dm.msg.Sound Then m.Put("sound", "default")
     jg.Initialize(CreateMap("aps":m))
     'jg.Initialize(CreateMap("aps":CreateMap("content-available": 1), "Key1": "Value1"))
     WriteItem(FrameData,2, jg.ToString.GetBytes("UTF8"))
     WriteItem(FrameData,3, bc.IntsToBytes(Array As Int(Rnd(0, 9999999))))
     WriteItem(FrameData,4, bc.IntsToBytes(Array As Int(dm.msg.ExpirationDate / 1000)))
     Dim p As Byte
     If dm.msg.HighPriority Then p = 10 Else p = 5
     WriteItem(FrameData,5, Array As Byte(p))
     Dim payload() As Byte = FrameData.ToBytesArray
     out.WriteBytes(Array As Byte(2), 0, 1)
     out.WriteBytes(bc.IntsToBytes(Array As Int(payload.Length)), 0, 4)
     out.WriteBytes(payload, 0, payload.Length)
   Loop
   astream.Write(out.ToBytesArray)
End Sub

Private Sub WriteItem(out As OutputStream, id As Byte, data() As Byte)
   out.WriteBytes(Array As Byte(id), 0, 1)
   out.WriteBytes(bc.ShortsToBytes(Array As Short(data.Length)), 0, 2)
   out.WriteBytes(data, 0, data.Length)
End Sub
 
Upvote 0
Top