0

I'm sending silent push notification every 30 minutes, I want execute code when silent notification arrives to device. But after lot of tries, I can't get the result. When I testing it on my device (with version from Xcode) everything works, after uploading it to TestFlight and downloading the version from TestFlight I'm not able to wake the app from background or wake it from terminated state. Simply this code executes after launching an app or app goes to foreground.

According to Apple documentation I should be able to wake the app and execute the 30 seconds of a code. I verified that silent notification is successfully delivered. I have missing something?

AppDelegate.swift

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
    }

    guard (userInfo["aps"] as? [String?: Any]) != nil else {
        Analytics.logEvent("fetch_failed", parameters: nil)
        completionHandler(.failed)
        return
    }

    let login = UserDefaults.standard.value(forKey: "username") as? String
    let password = UserDefaults.standard.value(forKey: "password") as? String

    if login != nil && password != nil {
        let session = URLSession.shared
        let url = URL(string: "https://example.com")!
        let body_values = Data(("credentials...").utf8)
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36", forHTTPHeaderField: "User-Agent")
        request.httpBody = body_values

        let loadDataTask = session.dataTask(with: request) { data, response, error in
            if let httpResponse = response as? HTTPURLResponse {
                print(httpResponse.statusCode)
                if httpResponse.statusCode == 200 {
                    if let data = data, let dataString = String(data: data, encoding: .utf8) {
                        let htmlparser: HTMLParser = HTMLParser()
                        let numberOfEmails = htmlparser.getXPathvalue(xPath: "/html/body/div[1]/div[3]/div[3]/a[1]", fromHtml: dataString)
                        self.setNotification(title: "New emails!", body: "Count of new emails: \(numberOfEmails)")
                        completionHandler(.newData)
                    }
                    else {
                        completionHandler(.failed)
                    }
                }
                else {
                    completionHandler(.failed)
                }
            }
            else {
                completionHandler(.failed)
            }
        }
        loadDataTask.resume()
    }
    else {
        completionHandler(.failed)
    }
}

PushNotification.js

var message = {
  notification: {
  },
  apns: {
     headers: {
        'apns-priority' : '5',
        'apns-push-type' : 'background'
     },
     payload: {
         aps: {
            'content-available' : 1
         }
     }
   },
   topic: topic
};
Peter Hlavatík
  • 117
  • 1
  • 1
  • 11
  • In addition to Caio’s observations, you’re completion handler logic is not right. Let’s say that the network request was successful: You’re going to call the completion handler three times with `.newData`, `.noData`, and `.failed`! Second, if the network request failed for some reason, you’re not going to call the completion handler at all. Third, if login or password was nil, you’re not calling the completion handler, either. Bottom line, make sure that every path of execution calls the completion handler precisely once. – Rob Mar 28 '20 at 15:13
  • 1
    When testing this sort of stuff, I love [unified logging](https://developer.apple.com/documentation/os/logging), so I can watch logging messages from my iOS app from my macOS console without running it via the Xcode debugger, which can change the app lifecycle. For demonstration, see [Unified Logging and Activity Tracing](https://developer.apple.com/videos/play/wwdc2016/721/) video. – Rob Mar 28 '20 at 15:21
  • If you forgive the unrelated observations: 1. Make sure to include the “https://“ scheme to your URL. 2. I’d be wary about just adding a password to the request unencoded. What if it had a reserved character like `&` in it? I’d suggest [percent-encoding](https://stackoverflow.com/a/26365148/1271826) the values in your `x-www-form-urlencoded` request. – Rob Mar 28 '20 at 15:22
  • Hi @Rob, thanks for comment. I updated the code here so you can take a look. But even when I have completion handlers everywhere, enabled Background Fetch and Remote notifications I'm able to get silent push with execution only on my device with version from Xcode, Testflight version not working... I'm totally lost. – Peter Hlavatík Mar 28 '20 at 20:11

1 Answers1

0

There are two main things you must pay attention to that are discussed in this apple forum discussion!. The first one is that the app can't be terminated, otherwise it won't launch the app, it must be foreground or background. And the other one is that you need to enable the background mode in Info.plist to make it work. Other than that there are situations when the system won't just refresh your app, the greatest guarantee you have is only if the user clicks a notification your app receives, otherwise for some privacy concerns it may not work.

Caio Gomes
  • 19
  • 1
  • 3