Search code examples
swiftpromisealamofirepromisekit

Poll url until response is a certain value


I've just begun coding in Swift (a very nice language) and I am trying to make an app that requires the user to login using a third party login service.

The basics of the authentication flow looks like this: 1. User enters ssn (swedish personnummer) and hits enter.
2. POST to a url that returns the json blob:

{
    "transactionId": "a transaction id",
    "expires": "date sting in some iso format",
    "autostartToken": "irrelevant for my usage"
}

3. Poll a url that uses the transactionId from step 2.
This url returns a json blob:

{
    "state": "OUTSTANDING_TRANSACTION",
    // other stuff that's uninteresting
}

This url will return a more complex json blob once the user has granted access using a mobile authentication app. The state will then change to "COMPLETED". 4. Receive the authentication token from a final url that can be attained from the blob in step 3 (once the state is "COMPLETED".
5. ???
6. Profit!

So my "problem" is that I can't really figure out (with my limited swift knowledge) how to do step 3. Poll the url until the state is "COMPLETED" (or the expires from step 2 is passed, and it should fail).

I have done a hacky attempt in javascript to try the service out, and it sort of looks like this:

this.postMethodThatReturnsAPromise(url, data).then(response => {
    let {transactionId} = response.body;
        let self = this,
            max = 10,
            num = 0;
        return new Promise(function (resolve, reject) {
            (function poll() {
                self._get(`baseurl/${transactionId}`).then(res => {
                    let {state} = res.body;
                    if (state !== 'COMPLETE' && num < max) {
                        setTimeout(poll, 2000);
                    } else if (state === 'COMPLETE') {
                        return resolve(res);
                    }
                });
                num++;
            })();
        });
    })

How can I do this in swift 3 and using Alamofire and Promisekit?

return Alamofire.request(url, method: .post, /* rest is omitted */).responseJSON().then { response -> String in
    let d = res as! Dictionary<String, Any>
    return d["transactionId"]
}.then { transactionId -> [String: Any] in
    // TODO: The polling until blob contains "state" with value "COMPLETED"
    // Return the final json blob as dict to the next promise handler
}.then { data in
}

Solution

  • Here's what I came up with that seem to work ok.

    }.then { transactionId -> Promise<PMKDataResponse> in
        var dataDict: Dictionary<String, Any> = ["state": "unknown"]
    
        return Promise { fullfill, reject in
            func poll() {
                // Method that fires an Alamofire get request and returns a Promise<PMKDataResponse>
                self._get("baseurl/\(transactionId)").then { response -> Void in
                    // .toDictionary() is a extension to Data that converts a Data into a dictionary.
                    dataDict = response.data.toDictionary()
                    let state: String = dataDict["state"] as! String
                    if (state != "COMPLETE") {
                        after(interval: 2).then {
                            poll()
                        }
                    } else if (state == "COMPLETE") {
                        fullfill(response)
                    }
                }
            }
            poll()
        }
    }
    

    Obviously this won't check the expiration date for the transaction, but that's ok for now. Oh and the lack of error handling...👌