0xTim 0xTim - 5 months ago 119
Swift Question

Unsupported URL when app receives a redirect to itself

So have an interested issue when sending out an OAuth2 token request (to a server I control). The server acts correctly and returns a 302 redirect to 'com.myapp://oauth/callback#token_type=Bearer&access_token=WPtSq5oFW0moitYabRmymbVxgZTGnc&expires_in=3600&scope=register' however my app throws up an NSURLErrorDomain Code =-1002 "Unsupported URL".

And yes I've registered the URL scheme in my info.plist, which contains:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.mycompany.myapp</string>
</dict>
</array>


Has anyone got any idea as to why this isn't redirecting properly and getting sent to my AppDelegate.swift? It's driving me a bit mad! I can happily put the redirect URL into Safari and it open my app fine. The full error I see in the console is:

Error: Optional(Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSUnderlyingError=0x1357e1c80 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}, NSErrorFailingURLStringKey=com.myapp://oauth/callback#token_type=Bearer&access_token=WPtSq5oFW0moitYabRmymbVxgZTGnc&expires_in=3600&scope=anonymous_view, NSErrorFailingURLKey=com.myapp://oauth/callback#token_type=Bearer&access_token=WPtSq5oFW0moitYabRmymbVxgZTGnc&expires_in=3600&scope=anonymous_view, NSLocalizedDescription=unsupported URL})


I should add, I'm sending the request using NSURLSession as below:

let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let url = NSURL(string: authoriseUrl)
let dataTask = defaultSession.dataTaskWithURL(url!) {data, response , error in
print("Data: \(data)")
print("Response: \(response)")
print("Error: \(error)")
}

dataTask.resume()

Answer

Managed to make it work. Turns out that an NSURLSession doesn't use the registered URL schemes in the info.plist like Safari does. So you need to write an NSURLProtocol subclass which can handle your URL scheme:

class MyUrlProtocol: NSURLProtocol {

override class func canInitWithRequest(request: NSURLRequest) -> Bool {
    if request.URL?.scheme == "myapp" {
        return true
    }
    return false
}

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
    return request
}

override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
                                             toRequest bRequest: NSURLRequest) -> Bool {
    return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}

override func startLoading() {
    // Do your work here
}

override func stopLoading() {
}

Then in your NSURLSession, you need to add it to your configuration before making your request:

    var protocolClasses = [AnyClass]()
    protocolClasses.append(MyUrlProtocol)

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    configuration.protocolClasses = protocolClasses

    let defaultSession = NSURLSession(configuration: configuration)
    let url = NSURL(string: authoriseUrl)
    let dataTask = defaultSession.dataTaskWithURL(url!) {data, response , error in
        print("Data: \(data)")
        print("Response: \(response)")
        print("Error: \(error)")
    }

    dataTask.resume()