Search code examples
iosswiftwatchos-3wkrefreshbackgroundtask

WatchOS 3 WKApplicationRefreshBackgroundTask didReceiveChallenge


I have finally (ignoring the sample code which I never saw work past "application task received, start URL session") managed to get my WatchOS3 code to start a background URL Session task as follows:

 func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {

    for task in backgroundTasks {
        if let refreshTask = task as? WKApplicationRefreshBackgroundTask {
            // this task is completed below, our app will then suspend while the download session runs
            print("application task received, start URL session")

            let request = self.getRequestForRefresh()
            let backgroundConfig = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)
            backgroundConfig.sessionSendsLaunchEvents = true
            backgroundConfig.httpAdditionalHeaders = ["Accept":"application/json"]
            let urlSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
            let downloadTask = urlSession.downloadTask(with: request)

            print("Dispatching data task at \(self.getTimestamp())")
            downloadTask.resume()

            self.scheduleNextBackgroundRefresh(refreshDate: self.getNextPreferredRefreshDate())
            refreshTask.setTaskCompleted()
        }
        else if let urlTask = task as? WKURLSessionRefreshBackgroundTask {
            //awakened because background url task has completed
            let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: urlTask.sessionIdentifier)
            self.backgroundUrlSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil) //set to nil in task:didCompleteWithError: delegate method

            print("Rejoining session ", self.backgroundUrlSession as Any)
            self.pendingBackgroundURLTask = urlTask //Saved for .setTaskComplete() in downloadTask:didFinishDownloadingTo location: (or if error non nil in task:didCompleteWithError:) 

        } else {
            //else different task, not handling but must Complete all tasks (snapshot tasks hit this logic)
            task.setTaskCompleted()
        }
    }
}

However, the issue I am now seeing is that my delegate method urlSession:task:didReceiveChallenge: is never being hit, so I cannot get my download to complete. (I have also added the session level urlSession:didReceiveChallenge: delegate method and it is also not being hit).

Instead I immediately hit my task:didCompleteWithError: delegate method which has the error:

"The certificate for this server is invalid. You might be connecting to a server that is pretending to be ... which could put your confidential information at risk."

Has anyone gotten the background watch update to work with the additional requirement of hitting the didReceiveChallenge method during the background URL session?

Any help or advice you can offer is appreciated.


Solution

  • As it turns out the server certificate error was actually due to a rare scenario in our test environments. After the back end folks gave us a work around for that issue this code worked fine in both our production and test environments.

    I never hit urlSession:task:didReceiveChallenge: but it turned out I did not need to.

    Made a minor un-related change:
    Without prints/breakpoints I was sometimes hitting task:didCompleteWithError Error: like a ms before I hit downloadTask:didFinishDownloadingTo location:.

    So I instead set self.pendingBackgroundURLTask completed in downloadTask:didFinishDownloadingTo location:. I only set it completed in task:didCompleteWithError Error: if error != nil.

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    
        //Complete task only if error, if no error it will be completed when download completes (avoiding race condition)
        if error != nil {
            self.completePendingBackgroundTask()
        }
    
    }
    
    func completePendingBackgroundTask()
    {
        //Release the session
        self.backgroundUrlSession = nil
    
        //Complete the task
        self.pendingBackgroundURLTask?.setTaskCompleted()
        self.pendingBackgroundURLTask = nil
    }
    

    Hope someone else finds this helpful.