iosswiftapple-push-notificationsapns-phpunnotificationserviceextension

APNS shows just one pending notification


I 've successfully implemented push notification service for my ios app. I 've generated the required certificates and the code works. The problem arises when the device is disconnected from the internet and receives some notifications (which are pending and not displayed), then when the device is connected to the internet again ...only one of the pending apns push notifications are displayed. I am using php for my backend and NotificationServiceExtension for attachments etc.

Here is my php code

public static function sendAPNS($token,$data)
    {
      print_r($token);
      $apnsServer = 'ssl://gateway.push.apple.com:2195';
      $privateKeyPassword = 'password-here';
      /* Device token */
      $deviceToken = $token;
      $pushCertAndKeyPemFile = 'nameofthefile.pem';
      $stream = stream_context_create();
      stream_context_set_option($stream, 'ssl', 'passphrase', $privateKeyPassword);
      stream_context_set_option($stream, 'ssl', 'local_cert', $pushCertAndKeyPemFile);
      $connectionTimeout = 20;
      $connectionType = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
      $connection = stream_socket_client($apnsServer, $errorNumber, $errorString, $connectionTimeout, $connectionType, $stream);




        /*Alert array eg. body, title etc */
      $alertArray = [];
      $alertArray["body"] = $data["body"];
      $alertArray["title"] =$data["title"];


      $messageBody['aps'] = array(
        'alert' => $alertArray,
        'sound' => 'default',
        'category'=> 'customUi',
        'mutable-content'=>1

      );
      /*User Info*/
      $messageBody["attachment-url"] = $data["url"];
      $messageBody["type_code"] = $data["type_code"];
      $messageBody["ref_id"] = $data["ref_id"];
      $messageBody["user_to"] =$data["user_to"];

      /*Could be here*/



      $payload = json_encode($messageBody);

       print_r($payload);
      $notification = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
      $wroteSuccessfully = fwrite($connection, $notification, strlen($notification));


      fclose($connection);
    }

My Service extension is as follows :-

class NotificationService: UNNotificationServiceExtension {

var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    if let bestAttemptContent = bestAttemptContent {


        var defaultsUser: UserDefaults = UserDefaults(suiteName: "group.shared.com.plates215")!
        if let countNot = defaultsUser.value(forKey: "notUniversal")
        {
            var   IntcountNot = countNot as! Int
            IntcountNot = IntcountNot + 1
            var sum: NSNumber = NSNumber(value: IntcountNot)
            bestAttemptContent.badge = sum
            defaultsUser.set(IntcountNot, forKey: "notUniversal") 

        }

        if let photo = bestAttemptContent.userInfo["attachment-url"] as? String
        {
            updateReadNots(userID: (bestAttemptContent.userInfo["user_to"] as? String)!)
            let url = NSURL(string: photo);
            var err: NSError?
            var imageData :NSData = try! NSData(contentsOf: url! as URL)
            var bgImage = UIImage(data:imageData as Data)

            if let attachment = UNNotificationAttachment.create(identifier: "colo", image: bgImage!, options: nil)
            {
                bestAttemptContent.attachments = [attachment]
                contentHandler(bestAttemptContent)
            }


        }




    }

}


override func serviceExtensionTimeWillExpire() {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
        contentHandler(bestAttemptContent)
    }
}
func updateReadNots(userID: String)
{
    var url: String = "api-url"
    let session: URLSession = URLSession(configuration: URLSessionConfiguration.default)
    var urlDown = URL(string: url)
    let downloadTask = session.downloadTask(with: urlDown!) { (url, rsp, error) in


    }
    downloadTask.resume()


}

     }

    extension UNNotificationAttachment {

static func create(identifier: String, image: UIImage, options: [NSObject : AnyObject]?) ->  UNNotificationAttachment? {
    let fileManager = FileManager.default
    let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
    let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
    do {
        try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
        let imageFileIdentifier = identifier+".png"
        let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
        guard let imageData = UIImagePNGRepresentation(image) else {
            return nil
        }
        try imageData.write(to: fileURL)
        let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
        return imageAttachment
    } catch {
        print("error " + error.localizedDescription)
    }
    return nil
}
  }

SOLUTION
As per @Li Sim 's link, this is what i did ...every notification that i send from my server contains not only the latest notification, but also every single unread notification seperated by a "\n". And then ios notifications properly display them. To keep a track of read and unread notifications, i have a mantained a read_status key in my back_end which is updated as soon as the notification is recieved in the NotificationContentExtension (https://developer.apple.com/documentation/usernotifications/unnotificatio nserviceextension). It is used to modify content and execute some code whenever an apns push is recieved. Hope this helps someone in the future :)


Solution

  • This is a behaviour documented by Apple before. Link to document: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1

    If a device is offline, sending a notification request targeting that device causes the previous request to be discarded. If a device remains offline for a long time, all its stored notifications in APNs are discarded.

    Though I'm unfamiliar with this, (sorry about that) here are some post that I found useful for your issue: How does whatsapp receive multiple notification when APNS stores only one in case device is offline?

    Hopefully, you'll get something out of it. Another way I can think of is to use LocalNotifications which is offline-compatible but you would have to figure out a way to track which notification is unread by the user. (A status of somekind perhaps)